2009-12-03 7 views
35

Я всегда настроить метаклассами что-то вроде этого:Есть ли какая-либо причина для выбора __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 

Есть ли основания предпочесть один над другим ?

Update: Имейте в виду, что я спрашиваю об использовании __new__ и __init__ в метаклассе. Я уже понимаю разницу между ними в другом классе. Но в метаклассе я не могу использовать __new__ для реализации кэширования, потому что __new__ вызван только созданием класса в метаклассе.

ответ

40

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

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

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

+0

Значительно яснее. И это имеет смысл. Есть ли причина использовать '__init__'? –

+5

'__new__' может делать все, что может сделать' __init__', но не наоборот. '__init__' может быть более удобным в использовании, так же, как' __new__' и '__init__' связаны с регулярной реализацией класса. Вы должны помнить, например, что-то возвращать из '__new__', но с' __init__' вы этого не делаете. –

+0

@JasonBaker Я понимаю, что это 5-летняя тема, и я не ожидаю ответа. Не хотите ли вы использовать __init__ всякий раз, когда вам нужно создать экземпляр класса, о котором идет речь. Как с [flyweight pattern] (http://en.wikipedia.org/wiki/Flyweight_pattern). –

1

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

3

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

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

+0

Я понимаю разницу между '__new__' и' __init__' для обычного класса. Я спрашивал, почему я хотел бы использовать их для метакласса. –

+0

Ах. Не беспокойся в таком случае. ^.^ –

+0

@JasonBaker Механизм точно такой же. Классы - это объекты их метаклассов, поэтому их создание работает одинаково. – glglgl

1

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

'__init__' вызывается с параметром' name', установленным на исходное имя, поэтому с теми же аргументами, что и '__new__'. Но 'cls .__ name__' должно быть новым в то время. – glglgl

+0

Т.е., с 'print ', я все еще называюсь« + name + »в init, но имеет« + cls .__ name__ + »« »« вы получаете 'меня все еще называют« A »в init, но имеют« A_class_named_A » '. – glglgl