Интересно немного о разнице между __new__
базового класса и __call__
метакласса.
Рассмотрим пример кода:
class Metaclass(type):
def __call__(cls, *args):
print("Metaclass.__call__", cls, args)
obj = type.__call__(cls, *args)
return obj # cls.__init__ was already called
class DemoWithMetaclass(metaclass=Metaclass):
def __init__(self, x):
print("DemoWithMetaclass.__init__", x)
self.x = x
class Baseclass(object):
def __new__(cls, *args):
print("Baseclass.__new__", cls, args)
obj = super(Baseclass, cls).__new__(cls)
return obj # cls.__init__ not yet called
class DemoWithBaseclass(Baseclass):
def __init__(self, x):
print("DemoWithBaseclass.__init__", x)
self.x = x
demo1 = DemoWithMetaclass(1)
print("got:", demo1, demo1.x)
demo2 = DemoWithBaseclass(2)
print("got:", demo2, demo2.x)
Вывод будет:
Metaclass.__call__ <class '__main__.DemoWithMetaclass'> (1,)
DemoWithMetaclass.__init__ 1
got: <__main__.DemoWithMetaclass object at 0x103b2cb90> 1
Baseclass.__new__ <class '__main__.DemoWithBaseclass'> (2,)
DemoWithBaseclass.__init__ 2
got: <__main__.DemoWithBaseclass object at 0x103b34e10> 2
Одно отличие состоит в том, что через базовый класс __new__
вы не можете переводить аргументы или что-то подобное. В зависимости от варианта использования это может быть незначительным или несущественным отличием. Метакласс обеспечивает дальнейшее метапрограммирование через __new__
или __prepare__
.
Оба могут использоваться для реализации синглетонов. Когда вы используете базовый класс __new__
для возврата существующего экземпляра в некоторых случаях, __init__
будет вызываться на уже существующем экземпляре, где __init__
уже был вызван ранее, поэтому вам, возможно, придется позаботиться об этом.
Есть ли другие различия? Или причины использовать метакласс вместо базового класса, предполагая, что нам не нужны какие-либо другие функции метапрограммирования?