Есть ли причина выбирать __new__ вместо __init__ при определении метакласса? - PullRequest
40 голосов
/ 03 декабря 2009

Я всегда настраивал метаклассы примерно так:

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__ вызывается только при создании класса в метаклассе.

Ответы [ 4 ]

48 голосов
/ 03 декабря 2009

Если вы хотите изменить атрибуты dict до создания класса или изменить кортеж base, вы должны использовать __new__. К тому времени, когда __init__ видит аргументы, объект класса уже существует. Кроме того, вы должны использовать __new__, если хотите вернуть что-то, кроме недавно созданного класса рассматриваемого типа.

С другой стороны, к моменту запуска __init__ класс уже существует. Таким образом, вы можете делать такие вещи, как давать ссылку на только что созданный класс одному из его объектов-членов.

Редактировать : изменена формулировка, чтобы было более понятно, что под "объектом" я имею в виду класс-объект.

4 голосов
/ 03 декабря 2009

Вы можете увидеть полную запись в официальных документах , но в основном __new__ называется до создания нового объекта (с целью его создания) и __init__ называется после создается новый объект (с целью его инициализации).

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

1 голос
/ 12 марта 2011

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

1 голос
/ 03 декабря 2009

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

...