Is there any reason to choose __new__ over __init__ when defining a metaclass?
If you want to alter the attributes dict before the class is created, or change the bases tuple, you have to use __new__
. By the time __init__
sees the arguments, the class object already exists. Also, you have to use __new__
if you want to return something other than a newly created class of the type in question.
On the other hand, by the time __init__
runs, the class does exist. Thus, you can do things like give a reference to the just-created class to one of its member objects.
Edit: changed wording to make it more clear that by "object", I mean class-object.
You can see the full writeup in the official docs, but basically, __new__
is called before the new object is created (for the purpose of creating it) and __init__
is called after the new object is created (for the purpose of initializing it).
Using __new__
allows tricks like object caching (always returning the same object for the same arguments rather than creating new ones) or producing objects of a different class than requested (sometimes used to return more-specific subclasses of the requested class). Generally, unless you're doing something pretty odd, __new__
is of limited utility. If you don't need to invoke such trickery, stick with __init__
.
Several differences, in fact.
For one thing, the first argument in __new__
and __init__
are not the same, which isn't helped by everyone just using, cls
. Someone pointed this out and it's core to understanding the difference:
__new__
gets the metaclass -MyType
in my example (remember the application-level class is not created yet). This is where you can alterbases
(which can cause MRO resolution errors if you're not careful).__init__
gets the newly-created application-level class,Bar
andFoo
and, by that time, this class's namespace has been populated, seecls_attrib
in example below.
Sample code:
class Mixin: passclass MyType(type): def __new__(mcls, name, bases, attrs, **kwargs): print(" MyType.__new__.mcls:%s" % (mcls)) if not Mixin in bases: #could cause MRO resolution issues, but if you want to alter the bases #do it here bases += (Mixin,) #The call to super.__new__ can also modify behavior: # 👇 classes Foo and Bar are instances of MyType return super(MyType, mcls).__new__(mcls, name, bases, attrs) #now we're back to the standard `type` #doing this will neuter most of the metaclass behavior, __init__ wont #be called. 👇 #return super(MyType, mcls).__new__(type, name, bases, attrs) def __init__(cls, name, bases, attrs): print(" MyType.__init__.cls:%s." % (cls)) #I can see attributes on Foo and Bar's namespaces print(" %s.cls_attrib:%s" % (cls.__name__, getattr(cls, "cls_attrib", None))) return super().__init__(name, bases, attrs)print("\n Foo class creation:")class Foo(metaclass=MyType): passprint("\n bar class creation:")class Bar(Foo): #MyType.__init__ will see this on Bar's namespace cls_attrib = "some class attribute"
output:
Foo class creation: MyType.__new__.mcls:<class '__main__.test.<locals>.MyType'> MyType.__init__.cls:<class '__main__.test.<locals>.Foo'>. Foo.cls_attrib:None Bar class creation: MyType.__new__.mcls:<class '__main__.test.<locals>.MyType'> MyType.__init__.cls:<class '__main__.test.<locals>.Bar'>. Bar.cls_attrib:some class attribute