метакласс __call__ против базового класса __new__ - PullRequest
1 голос
/ 26 апреля 2020

Интересно немного о разнице между __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__ уже был вызван ранее, поэтому вам, возможно, придется позаботиться об этом.

Есть ли другие различия? Или причины использовать метакласс вместо базового класса, предполагая, что нам не нужны какие-либо другие функции метапрограммирования?

...