В чем разница между типом и типом.__ 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
Все это довольно хорошо описано здесь.
Если вы не вернете правильный тип объекта, нет смысла определять пользовательский метакласс.