pythonclassdynamicdynamic-class-creationdynamic-class

What is the difference between class syntax and type()?


I am well aware of fact that classes can be declared dynamically in python using type and have used it here and there. But I am still unclear what are the differences between these two functions.

def class_factory():
    def init(self, attr):
        self.attr = attr
    class_ = type('DynamicClass', (), {'__init__' : init})
    return class_

and

def class_factory():
    class DynamicClass:
        def __init__(self, attr):
            self.attr = attr
    return DynamicClass

I have seen first pattern used in a lot of code-bases (like django) but never seen second one so far. Still I find second one more clear, syntactically.

I am struggling to find proper explanation about why people use first pattern for declaring dynamic classes and not second one. I experimented with both functions and could not find any notable differences between classes obtained from both functions. I am hoping for a clear explanation about differences between above two patterns/syntaxes from any aspect (performance-wise or any other) they differ.

Thanks,


Solution

  • A class statement is a declarative syntax for a call to a metaclass.

    class Foo(Bar, metaclass=SomeMeta):  # If not specified, the metaclass is type
        ...
    

    is equivalent to

    Foo = SomeMeta('Foo', (Bar,) {...})
    

    where {...} is some mapping constructed from the body of the class statement. Just as a simple example, without going into full detail:

    class Foo(Bar, metaclass=SomeMeta):
        some_attr = "foo string"
    
        def __init__(self, x=9):
            self.y = x = 3
    

    would define a function name __init__, then pass {'__init__': __init__, 'some_attr': 'foo string'} to SomeMeta. The name __init__ only exists in a temporary namespace created by the class statement, without affecting any name __init__ that might exist outside the statement.

    To answer your question, sometimes it is more flexible or simpler to construct the third argument the metaclass yourself than to let the class statement do it for you. The same goes for the second argument. The first argument is fixed by the syntax of the class statement, but could be generated at run-time if you call the metaclass yourself.


    Note that this means you can really abuse the class statement. EnumMeta from the enum module, in my opinion, stretches what you should do with one. Here's a really abusive example. The metaclass doesn't actually have to be a type; it just has to be callable, and it needs to accept three positional arguments. It can accept additional keyword arguments as well. It can then do anything it wants with those arguments, and return anything it wants as well.

    def FooMeta(name, bases, dct, msg):
        print(f'{msg} from {name}')
        return 3
    
    class Foo(metaclass=FooMeta, msg="hi"):
        pass
    

    Foo isn't even a class; it's bound to 3.