Есть ли причина выбирать __new__ над __init__ при определении метакласса?
Я всегда настраивал метаклассы примерно так:
class SomeMetaClass(type):
def __new__(cls, name, bases, dict):
#do stuff here
Но я просто столкнулся с метаклассом, который был определен следующим образом:
class SomeMetaClass(type):
def __init__(self, name, bases, dict):
#do stuff here
Есть ли какая-то причина предпочесть друг другу?
Обновить. Имейте в виду, что я прошу об использовании __new__
и __init__
в метаклассе. Я уже понимаю разницу между ними в другом классе. Но в метаклассе я не могу использовать __new__
для реализации кэширования, потому что __new__
вызван только созданием класса в метаклассе.
Ответы
Ответ 1
Если вы хотите изменить атрибуты dict перед созданием класса или изменить базовый кортеж, вы должны использовать __new__
. К моменту появления __init__
аргументов объект класса уже существует. Кроме того, вы должны использовать __new__
, если хотите вернуть что-то другое, кроме вновь созданного класса соответствующего типа.
С другой стороны, к моменту выполнения __init__
класс существует. Таким образом, вы можете делать что-то вроде ссылки на только что созданный класс на один из его объектов-членов.
Изменить: изменилась формулировка, чтобы сделать более понятным, что под "объектом" я подразумеваю класс-объект.
Ответ 2
Вы можете увидеть полную запись в официальные документы, но в основном, __new__
вызывается до создания нового объекта (для цель его создания) и __init__
вызывается после создания нового объекта (с целью его инициализации).
Использование __new__
позволяет использовать трюки, такие как кэширование объектов (всегда возвращающее один и тот же объект для одних и тех же аргументов, а не создание новых) или создание объектов другого класса, чем запрошено (иногда используется для возврата более конкретных подклассов запрошенного класса). Как правило, если вы не делаете что-то довольно странное, __new__
имеет ограниченную полезность. Если вам не нужно ссылаться на такие обманки, придерживайтесь __init__
.
Ответ 3
Вы можете реализовать кэширование. Person("Jack")
всегда возвращает новый объект во втором примере, в то время как вы можете искать существующий экземпляр в первом примере с помощью __new__
(или не возвращать что-либо, если хотите).
Ответ 4
Как уже было сказано, если вы собираетесь изменить что-то вроде базовых классов или атрибутов, вы должны сделать это в __new__
. То же самое верно для name
класса, но, похоже, с ним есть особенность. Когда вы меняете name
, он не распространяется на __init__
, хотя, например, attr
есть.
Итак, у вас есть:
class Meta(type):
def __new__(cls, name, bases, attr):
name = "A_class_named_" + name
return type.__new__(cls, name, bases, attr)
def __init__(cls, name, bases, attr):
print "I am still called '" + name + "' in init"
return super(Meta, cls).__init__(name, bases, attr)
class A(object):
__metaclass__ = Meta
print "Now I'm", A.__name__
печатает
I am still called 'A' in init
Now I'm A_class_named_A
Это важно знать, если __init__
вызывает супер метакласс, который делает некоторую дополнительную магию. В этом случае нужно снова изменить имя перед вызовом super.__init__
.