В чем разница между типом и типом.__ new__ в python?

Я писал метакласс и случайно сделал это так:

class MetaCls(type):
    def __new__(cls, name, bases, dict):
        return type(name, bases, dict)

... вместо этого:

class MetaCls(type):
    def __new__(cls, name, bases, dict):
        return type.__new__(cls, name, bases, dict)

В чем же разница между этими двумя метаклассами? И более конкретно, что заставило первое работать некорректно (некоторые классы не были вызваны метаклассом)?

Ответ 1

В первом примере вы создаете целый новый класс:

>>> class MetaA(type):
...     def __new__(cls, name, bases, dct):
...         print 'MetaA.__new__'
...         return type(name, bases, dct)
...     def __init__(cls, name, bases, dct):
...         print 'MetaA.__init__'
... 
>>> class A(object):
...     __metaclass__ = MetaA
... 
MetaA.__new__
>>> 

а во втором случае вы вызываете родителя __new__:

>>> class MetaA(type):
...     def __new__(cls, name, bases, dct):
...         print 'MetaA.__new__'
...         return type.__new__(cls, name, bases, dct)
...     def __init__(cls, name, bases, dct):
...         print 'MetaA.__init__'
... 
>>> class A(object):
...     __metaclass__ = MetaA
... 
MetaA.__new__
MetaA.__init__
>>> 

Ответ 2

Первое, что вам нужно выяснить, это то, как работает object.__new__().

Вот это из документации:

object.__new__(cls[,...])

Вызывается для создания нового экземпляра класса cls. __new__() - это статический метод (в специальном случае, поэтому вам не нужно объявлять его как таковой), который принимает класс, экземпляр которого был запрошен, в качестве первого аргумента. Остальные аргументы передаются в выражение конструктора объекта (вызов класса). Возвращаемое значение __new__() должно быть новым экземпляром объекта (обычно это экземпляр cls).

Типичные реализации создают новый экземпляр класса, вызывая метод суперкласса __new__() с использованием super(currentclass, cls).__new__(cls[,...]) с соответствующими аргументами, а затем изменяя вновь созданный экземпляр по мере необходимости, прежде чем возвращать Это.

Если __new__() возвращает экземпляр cls, то будет вызываться новый метод __init__(), например __init__(self[,...]), где self - новый экземпляр, а остальные аргументы такие же, как и переданные в __new__().

Если __new__() не возвращает экземпляр cls, то новый метод __init__() вызываться не будет.

__new__() предназначен главным образом для того, чтобы подклассы неизменяемых типов (например, int, str или tuple) могли настраивать создание экземпляров. Он также обычно переопределяется в пользовательских метаклассах для настройки создания классов.

Так в мг. Ответ: первый не вызывает функцию __init__ а второй вызывает функцию __init__ после вызова __new__.

Ответ 3

Пожалуйста, обратитесь к аннотации ниже, надейтесь, что это поможет.

class MetaCls(type):
    def __new__(cls, name, bases, dict):
        # return a new type named "name",this type has nothing
        # to do with MetaCls,and MetaCl.__init__ won't be invoked
        return type(name, bases, dict)

class MetaCls(type):
    def __new__(cls, name, bases, dict):
        # return a new type named "name",the returned type 
        # is an instance of cls,and cls here is "MetaCls", so 
        # the next step can invoke MetaCls.__init__ 
        return type.__new__(cls, name, bases, dict)

Ответ 4

return type(name, bases, dict)

Что вы получаете от этого, это новый type, а не MetaCls экземпляр вообще. Следовательно, ваши методы, определенные в MetaCls (включая __init__), никогда не могут быть вызваны.

type.__new__ будет вызван как часть создания этого нового типа, да, но значение cls, входящего в эту функцию, будет type, а не MetaCls.

Ответ 5

class MyMeta(type):
    def __new__(meta, cls, bases, attributes):
        print 'MyMeta.__new__'
        return type.__new__(meta, cls, bases, attributes)
    def __init__(clsobj, cls, bases, attributes):
        print 'MyMeta.__init__'

class MyClass(object):
  __metaclass__ = MyMeta
  foo = 'bar'

Другой способ добиться того же результата:

cls = "MyClass"
bases = ()
attributes = {'foo': 'bar'}
MyClass = MyMeta(cls, bases, attributes)

MyMeta является вызываемым, поэтому Python будет использовать специальный метод __call__.

Python будет искать __call__ в типе MyMeta (который в нашем случае type)

"Для классов нового стиля неявные вызовы специальных методов только гарантированно работает корректно, если он определен по типу объектов, а не в словаре экземпляров объектов

MyClass = MyMeta(...) интерпретируется как:

my_meta_type = type(MyMeta)
MyClass = my_meta_type.__call__(MyMeta, cls, bases, attributes)

Внутри type.__call__() я представляю себе что-то вроде этого:

MyClass = MyMeta.__new__(MyMeta, cls, bases, attributes)
meta_class = MyClass.__metaclass__
meta_class.__init__(MyClass, cls, bases, attributes)
return MyClass

MyMeta.__new__() будет определять способ построения MyClass:

type.__new__(meta, cls, bases, attributes) установит правильный метакласс (который MyMeta) для MyClass

type(cls, bases, attributes) установит метаклас по умолчанию (который является типом) для MyClass

Ответ 6

Все это довольно хорошо описано здесь.

Если вы не вернете правильный тип объекта, нет смысла определять пользовательский метакласс.