4

I am trying to create nested Python classes using the 3 argument type function. I want to construct an analogue of this:

In [15]: class CC: 
    ...:     class DD: 
    ...:         pass 
    ...:                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       

The naive attempt is

In [17]: AA = type('AA', (), {'BB': type('BB', (), {})})                                                                                                                                                                                                                                                                                                                                                                                                                                       

but that is not quite right, since BB is actually created outside and before AA and is only put inside `AA later.

The difference isdemonstrated by:

In [18]: AA.BB                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 
Out[18]: __main__.BB

In [16]: CC.DD                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 
Out[16]: __main__.CC.DD

How can I create nested classes reflectively/dynamically that are completely equivalent to nested definitions?


I want to use this to reflectively generate a graphene-sqlalchemy api. The idiom there is to create an outer Graphene class with an inner Meta class pointing to the correponding SQLAchemy model class (http://docs.graphene-python.org/projects/sqlalchemy/en/latest/tutorial/#schema) eg:

from sqlalchemy import Column, Integer, String
from sqlalchemy.orm import relationship

from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class UserModel(Base):
    __tablename__ = 'department'
    id = Column(Integer, primary_key=True)
    name = Column(String)
    last_name = Column(String)

from graphene_sqlalchemy import SQLAlchemyObjectType

class User(SQLAlchemyObjectType):
    class Meta:
        model = UserModel
        # only return specified fields
        only_fields = ("name",)
        # exclude specified fields
        exclude_fields = ("last_name",)

The User class above is pretty cookie cutter and should be constructable programmatically from the UserModel class. This should then be doable for an entire schema.

Daniel Mahler
  • 7,653
  • 5
  • 51
  • 90
  • Why do you want to do this? Are you sure this is the solution to your problem? – roganjosh Nov 27 '18 at 20:31
  • 1
    @roganjosh Fairly sure. I am trying to reflectively generate a `graphene-sqlalchemy` api. The idiom is to create an outet Graphene class with an inner `Meta` class pointing to the correponding SQLAchemy model class. http://docs.graphene-python.org/projects/sqlalchemy/en/latest/tutorial/#schema – Daniel Mahler Nov 27 '18 at 20:43
  • 1
    @roganjosh i added a fuller explanation to the of the use case to the question. – Daniel Mahler Nov 27 '18 at 20:56
  • 1
    "but that is not quite right, since BB is actually created outside and before AA and is only put inside AA later" - nearly the same thing happens with CC and DD. DD is created before CC and inserted into the namespace before the creation of CC. – user2357112 Nov 27 '18 at 21:06
  • If you're working with SQLAlchemy, you're going to need to handle weird metaclass stuff, which may necessitate using something like [`types.new_class`](https://docs.python.org/3/library/types.html#types.new_class), depending on exactly what kind of weird metaclass stuff is involved. – user2357112 Nov 27 '18 at 21:08
  • @user2357112 That would be great. Do you have a reference for that? – Daniel Mahler Nov 27 '18 at 21:53
  • @DanielMahler: It's a straightforward consequence of [how class definitions are executed](https://docs.python.org/3/reference/compound_stmts.html#class-definitions). The body of a class definition is executed to initialize the namespace before the class object is created. The DD definition is in the body of the CC definition, so the DD class is created as part of executing the CC definition body before the CC class is created. – user2357112 Nov 27 '18 at 21:56

2 Answers2

3

In the Python 3.7 type documentation, it says:

The [1st argument] is the class name and becomes the __name__ attribute.

So, I think the only difference between your 2 examples is that AA.BB.__name__ is AA.BB in the 1st example, and BB in the 2nd. If you want the __name__ to be the same, you can do this:

AA = type('AA', (), {'BB': type('AA.BB', (), {})})

Otherwise, as far as I can tell, both examples are functionally equivalent.

torek
  • 448,244
  • 59
  • 642
  • 775
Remolten
  • 2,614
  • 2
  • 25
  • 29
3

Actually, the only difference you get there is the __qualname__ class attribute. The __qualname__ is created by the code object running the class body, and passed as an ordinary attribute to the metaclass __new__ method (usually type) .

Therefore all you need to get this level of equivalence is to explicitly pass a proper __qualname__ when creating the class:

In [9]: AA = type('AA', (), {'BB': type('BB', (), {'__qualname__': 'AA.BB'})})  

In [10]: AA
Out[10]: __main__.AA

In [11]: AA.BB
Out[11]: __main__.AA.BB

This will likely be enough for you, but beware that there are more subtle differences between classes created dynamically. One of the last metaclass questions I answered is exactly about that, but with the opposite approach: the challenge was to actually be able to distinguish classes created with both styles.

Detect if class was defined declarative or functional - possible?

(warning: that contains what likely is the "deepest black magic" Python code I ever put in an answer here)

jsbueno
  • 99,910
  • 10
  • 151
  • 209