2016-01-14 5 views
6

Я заинтересован в использовании функциональности __new__ для ввода кода в функцию подклассов __init__. Мое понимание из документации заключается в том, что python вызовет __init__ на экземпляр, возвращенный __new__. Тем не менее, мои попытки изменить значение __init__ в экземпляре, прежде чем возвращать его с __new__, похоже, не сработают.Использование __new__ для переопределения __init__ в подклассе

class Parent(object): 

    def __new__(cls, *args, **kwargs): 
     new_object = super(Parent, cls).__new__(cls) 
     user_init = new_object.__init__ 
     def __init__(self, *args, **kwargs): 
      print("New __init__ called") 
      user_init(self, *args, **kwargs) 
      self.extra() 
     print("Replacing __init__") 
     setattr(new_object, '__init__', __init__) 
     return new_object 

    def extra(self): 
     print("Extra called") 

class Child(Parent): 

    def __init__(self): 
     print("Original __init__ called") 
     super(Child, self).__init__() 

c = Child() 

Приведенный выше код печатает:

Replacing __init__ 
Original __init__ called 

, но я ожидал бы, чтобы напечатать

Replacing __init__ 
New __init__ called 
Original __init__ called 
Extra called 

Почему нет?

Я чувствую, что Python вызывает исходное значение __init__, независимо от того, что я установил в __new__. Выполнение интроспекции на c.__init__ показывает, что новая версия на месте, но она не была вызвана как часть создания объекта.

+0

Что вы думаете? – cat

ответ

0

Я подозреваю, что ответ заключается в том, что __init__ - это специальная функция, внутренне она определена как метод класса и в результате не может быть заменена переназначением ее в экземпляре объекта.

В Python все объекты представлены PyObject в C, в которых указатель на PyTypeObject. Он содержит член tp_init, который, как я считаю, содержит указатель на функцию __init__.

Другое решение работает, потому что мы модифицируем класс, а не экземпляр объекта.

+0

'PyTypeObject' представляет объект' type', а не все объекты, нет? –

+0

Argh. Ищет «PyObject» в исходном коде через github и вместо этого нашел '' PyTypeObject''. «PyObject'' имеет метод' 'ob_type'', который указывает на' 'PyTypeObject'', поэтому я думаю, что мои рассуждения все еще могут стоять. Это действительно зависит от того, как вызывается метод '' __init__''. –

+0

Переписан мой ответ, чтобы попытаться упростить. –

1

Ну, ожидается, что новый объект будет пуст до вызова __init__. Поэтому, вероятно, python, как оптимизация, не утруждает себя поиском объекта и отправляется на выбор __init__ прямо из класса.

Поэтому вам придется изменить __init__ подклассы. К счастью, у Python есть инструмент для этого, метаклассы.

В Python 2, вы установите метакласса, установив специальный элемент:

class Parent(object): 
    __metaclass__ = Meta 
    ... 

См Python2 documentation

В Python 3, вы установите метакласса с помощью атрибута ключевого слова в родительском списке, так

class Parent(metaclass=Meta): 
    ... 

См. Python3 documentation

metaclass - базовый класс для экземпляра класса. Он должен быть получен из type, а в нем __new__ он может изменить создаваемый класс (я считаю, что __init__ тоже нужно назвать, но примеры переопределяют __new__, поэтому я поеду с ним).__new__ будет похож на то, что у вас есть:

class Meta(type): 
    def __new__(mcs, name, bases, namespace, **kwargs): 
     new_cls = super(Meta, mcs).__new__(mcs, name, bases, namespace, **kwargs) 
     user_init = new_cls.__init__ 
     def __init__(self, *args, **kwargs): 
      print("New __init__ called") 
      user_init(self, *args, **kwargs) 
      self.extra() 
     print("Replacing __init__") 
     setattr(new_cls, '__init__', __init__) 
     return new_cls 

(на примере Python 3, но подпись в Python 2, кажется, то же самое за исключением того, что нет ни одного **kwargs, но добавление их не должно быть больно, я не проверял его).