new vs init
__new__ is the true constructor of class. It need to return a obj.__init__ initializes object. It doesn’t need to return anything.__new__ is a staticmethod: When class wants this magic method it will be looked up on the same object (the class), not on a upper level like all the other magic methods. This is very important to understand, because both the class and the metaclass can define this method
Think
__new__and__init__are two channels.__new__get called first and then__init__. Your arguments are passed separately to both functions except first one. This logic is embedded intype’s__call__. This is same for__new__.If
__init__is implemented and your class inherits from a base class, conventionally, you may still want to put parents__init__which bysuper().__init__()inside child’s__init__. It’s conventional since you may want to invoke initialization of parent__init__. However, if you choose not to includesuper().__init__(), that’s fine. This is same for__new__.
Example:
class Foo:
def __new__(cls, *args, **kwargs):
print('this is Foo __new__')
print(cls)
return super().__new__(cls)
def __init__(self, *args, **kwargs):
print('this is Foo __init__')
class foo(Foo):
def __new__(cls, *args, **kwargs):
print('this is foo __new__')
instance = super().__new__(cls, *args, **kwargs)
instance.attr = 100
return instance
def __init__(self, *args, **kwargs):
print('this is foo __init__')
super().__init__(*args, **kwargs)
>>> f = foo()
this is foo __new__
this is Foo __new__
<class '__main__.foo'>
this is foo __init__
this is Foo __init__
If
__new__get called by class (in metaclass’s __call__), since it’s a staticmethod, it will call class’s __new__. This will go to class attribute lookup. In default, it will run__new__by looking class.__dict__ which includes its parent__new__if necessary.If
__init__get called by instance (in metaclass’s __call__), since it’s not a staticmethod. it will call upper level __init__ which is defined in class. This will go to instance attribute lookup. In default, it will run__init__by looking Class.__dict__ which includes parent__init__if necessary.
Caution
Class.__dict__ is not a real dict but a special dict proxy that will also look in all the base classes.
For clarity I’ve simplified things to Class.__dict__[‘foobar’] but in reality the __dict__ proxy object is not used, but instead all the logic is inlined: attributes are looked up on the class of the object in the order of the __mro__ via typeobject.c:_PyType_Lookup (called from object.c: _PyObject_GenericGetAttrWithDict).
class Foo:
def __new__(cls):
print('this is Foo __new__')
print(cls)
return super().__new__(cls)
def __init__(self):
print('this is Foo __init__')
class foo(Foo):
pass
>>> f = foo()
this is Foo __new__
<class '__main__.foo'>
this is Foo __init__