My goal is to create a Factory class that will produce instances of slightly different classes based on real_base parameter. For simplicity I pass new base class directly, in reality base class would have been determined based on input parameter with some complex logic.
The problem is that if I use same class as base, I get different set of methods being invoked compared to using another class as base.
class SomeClass:
def __init__(self, *args, **kwargs):
print(f"{self.__class__.__name__}.__init__ got {args=}, {kwargs=}")
def __new__(cls, real_base=None, *args, **kwargs):
print(f"{cls.__name__}({__class__.__name__}).__new__ got {real_base=}, {args=}, {kwargs=}")
if not real_base: # If there is not real_base - act like regular class
return super().__new__(cls, *args, **kwargs)
# Otherwise - act like class factory
new_field_class = type(cls.__name__ + 'Custom', (real_base, ), {})
print(f"{cls.__name__} factory call")
res = new_field_class()
print(f"{cls.__name__} factory call returned {res} {res.__class__.mro()}")
return res
class OtherClass:
def __init__(self, *args, **kwargs):
print(f"{self.__class__.__name__} __init__ got {args=}, {kwargs=}")
def __new__(cls, *args, **kwargs):
print(f"{cls.__name__}({__class__.__name__}) __new__ got {args=}, {kwargs=}")
return super().__new__(cls, *args, **kwargs)
Here main logic is in SomeClass.__new__, while rest of methods just use print to show if they are getting called.
If I use this classes as follows:
SomeClass(OtherClass)
print()
SomeClass(SomeClass)
I get the following output:
SomeClass(SomeClass).__new__ got real_base=<class '__main__.OtherClass'>, args=(), kwargs={}
SomeClass factory call
SomeClassCustom(OtherClass) __new__ got args=(), kwargs={}
SomeClassCustom __init__ got args=(), kwargs={}
SomeClass factory call returned <__main__.SomeClassCustom object at 0x104fcfa10> [<class '__main__.SomeClassCustom'>, <class '__main__.OtherClass'>, <class 'object'>]
SomeClass(SomeClass).__new__ got real_base=<class '__main__.SomeClass'>, args=(), kwargs={}
SomeClass factory call
SomeClassCustom(SomeClass).__new__ got real_base=None, args=(), kwargs={}
SomeClassCustom.__init__ got args=(), kwargs={}
SomeClass factory call returned <__main__.SomeClassCustom object at 0x104fcfa40> [<class '__main__.SomeClassCustom'>, <class '__main__.SomeClass'>, <class 'object'>]
SomeClassCustom.__init__ got args=(<class '__main__.SomeClass'>,), kwargs={}
I understand why 3rd line is different - it literally calls different __new__ implementation.
What I don't understand is - why in second example there is an extra __init__ call ?
__new__()returns an instance of the containing class (including subclasses), then__init__()also gets called - even if the instance was already initialized, due to the nonstandard way you created it (by directly calling the class, rather than calling the inherited__new__().__new__of another class) then you should definitely cachenew_field_class = type(cls.__name__ + 'Custom', (real_base, ), {})SomeClassa class at all, and if there is a reason for it to be a class, why is it easier to use it to define instances of new classes than to create an instance ofSomeClassitself?SomeClasshave to do both? Why does it even make sense to try to make it do both?