Есть ли причина выбирать __new__ вместо __init__ при определении метакласса?
Автор вопроса: Jason BakerОтветов: 5
Я всегда устанавливал метаклассы примерно так:
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__ вызывается только при создании класса в метаклассе.
Если вы хотите изменить атрибуты dict перед созданием класса или изменить базовый кортеж, вы должны использовать __new__. К тому времени, когда __init__ увидит аргументы, объект класса уже существует. Кроме того, вы должны использовать __new__, если хотите вернуть что-то отличное от вновь созданного класса рассматриваемого типа.
С другой стороны, к моменту запуска __init__ класс уже существует. Таким образом, вы можете делать такие вещи, как дать ссылку на только что созданный класс одному из его объектов-членов.
Изменить: изменена формулировка, чтобы было более понятно, что под "объектом" я подразумеваю класс-объект.
Ответ 2 · рейтинг 7 · Ben Blank
Вы можете увидеть полное описание в официальной документации, но в основном __new__ вызывается перед созданием нового объекта (с целью его создания), а __init__ вызывается после создания нового объекта (с целью его инициализации).
Использование __new__ позволяет использовать такие трюки, как кэширование объектов (всегда возвращая один и тот же объект для одних и тех же аргументов, а не создавая новые) или создание объектов класса, отличного от запрошенного (иногда используется для возврата более конкретных подклассов запрошенного класса). Как правило, если вы не делаете что-то довольно странное, __new__ имеет ограниченную полезность. Если вам не нужно использовать такие трюки, придерживайтесь __init__.
Ответ 3 · рейтинг 4 · JL Peyret
На самом деле несколько отличий.
Во-первых, первый аргумент в __new__ и __init__ не один и тот же, что не помогает всем, кто просто использует cls. Кто-то указал на это, и это важно для понимания разницы:
__new__ получает метакласс - MyType в моем примере (помните, что класс уровня приложения еще не создан). Здесь вы можете изменить bases (что может привести к ошибкам разрешения MRO, если вы не будете осторожны).
__init__ получает недавно созданный класс уровня приложения, Bar и Foo, и к тому времени пространство имен этого класса уже заполнено, см. cls_attrib в примере ниже.
Образец кода
class Mixin:
pass
class MyType(type):
def __new__(mcls, name, bases, attrs, **kwargs):
print(" MyType.__new__.mcls:%s" % (mcls))
if not Mixin in bases:
#could cause MRO resolution issues, but if you want to alter the bases
#do it here
bases += (Mixin,)
#The call to super.__new__ can also modify behavior:
# ???? classes Foo and Bar are instances of MyType
return super(MyType, mcls).__new__(mcls, name, bases, attrs)
#now we're back to the standard `type`
#doing this will neuter most of the metaclass behavior, __init__ wont
#be called. ????
#return super(MyType, mcls).__new__(type, name, bases, attrs)
def __init__(cls, name, bases, attrs):
print(" MyType.__init__.cls:%s." % (cls))
#I can see attributes on Foo and Bar's namespaces
print(" %s.cls_attrib:%s" % (cls.__name__, getattr(cls, "cls_attrib", None)))
return super().__init__(name, bases, attrs)
print("\n Foo class creation:")
class Foo(metaclass=MyType):
pass
print("\n bar class creation:")
class Bar(Foo):
#MyType.__init__ will see this on Bar's namespace
cls_attrib = "some class attribute"
выход
Foo class creation:
MyType.__new__.mcls:<class '__main__.test.<locals>.MyType'>
MyType.__init__.cls:<class '__main__.test.<locals>.Foo'>.
Foo.cls_attrib:None
Bar class creation:
MyType.__new__.mcls:<class '__main__.test.<locals>.MyType'>
MyType.__init__.cls:<class '__main__.test.<locals>.Bar'>.
Bar.cls_attrib:some class attribute
Ответ 4 · рейтинг 2 · Debilski
Как уже было сказано, если вы собираетесь изменить что-то вроде базовых классов или атрибутов, вам придется сделать это в __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__ необходимо снова изменить имя.
Ответ 5 · рейтинг 1 · Otto Allmendinger
Вы можете реализовать кэширование. Person("Jack") всегда возвращает новый объект во втором примере, в то время как вы можете найти существующий экземпляр в первом примере с помощью __new__ (или ничего не возвращать, если хотите).