2015-05-18 3 views
0

Рассмотрим следующий код:не __init__ вызывается, когда subcalssing Dict и что-то еще

class Lockable(object): 

    def __init__(self): 
     self._lock = None 

    def is_locked(self): 
     return self._lock is None 


class LockableDict(dict, Lockable): 
    pass 

А теперь:

In [2]: l = example.LockableDict(a=1, b=2, c=3) 

In [3]: l.is_locked() 
--------------------------------------------------------------------------- 
AttributeError       Traceback (most recent call last) 
<ipython-input-3-4344e3948b9c> in <module>() 
----> 1 l.is_locked() 

/home/sven/utils/example.py in is_locked(self) 
     8 
     9  def is_locked(self): 
---> 10   return self._lock is None 

AttributeError: 'LockableDict' object has no attribute '_lock' 

Это выглядит так, как будто Lockable.__init__ не назывались вообще. Это так? Зачем?

Чтобы сделать более интересным, то получается, что это достаточно, чтобы изменить заголовок класса LockableDict так:

class LockableDict(Lockable, dict): 

, чтобы заставить его работать. Зачем?

+0

Если вы его не называете, это не называется. – laike9m

+0

Что это значит? Я не переопределяю '__init__' в' LockableDict', поэтому он должен генерировать '__init__', делая автоматические вызовы базовым классам' __init__', не так ли? Также он не объясняет, почему работает второй вариант ... – Sventimir

+0

Вы должны явно называть '__init__' в' __init__' LockableDict – RvdK

ответ

4

Ваше непонимание видно из этого комментария:

Я не переопределять в LockableDict__init__, поэтому он должен генерировать __init__ делать автоматические вызовы базовых классов __init__ с, не так ли?

Нет!

Во-первых, ничто автоматически не генерируется; разрешение метода происходит во время вызова.

И во время разговора Python не будет звонить каждый базовый класс __init__, он будет вызывать только первый найденный. *

Именно поэтому super существует. Если вы явно не вызываете super в переопределении, базовые классы или даже классы sibling не получат их реализации. И dict.__init__ не называет его super.

Также и ваш Lockable.__init__. Таким образом, это означает, что изменение ордера гарантирует, что Lockable.__init__ будет вызван ... но dict.__init__ сейчас не вызывается.


Итак, что вы на самом деле хотите сделать? Ну, алгоритм MRO Python разработан, чтобы обеспечить максимальную гибкость для иерархий , взаимодействующих с классами, с любыми не взаимодействующими классами, вставленными в конце. Таким образом, вы можете сделать это:

class Lockable(object): 

    def __init__(self): 
     super().__init__() # super(Lockable, self).__init__() for 2.x 
     self._lock = None 

    def is_locked(self): 
     return self._lock is None 


class LockableDict(Lockable, dict): 
    pass 

заметить также, что это позволяет передавать аргументы инициализатора по цепочке через все ваши взаимодействующие классы, а затем просто назвать недружественный класс __init__ с аргументами вы знаете, он нуждается.

В некоторых случаях сначала нужно поставить недружелюбный класс. ** В этом случае, вы должны явно вызвать вокруг них:

class LockableDict(dict, Lockable): 
    def __init__(self): 
     dict.__init__(self) 
     Lockable.__init__(self) 

Но, к счастью, это не появляется очень часто.


* В стандартной method resolution order, что несколько сложнее, чем можно было ожидать. Это же правило MRO применяется, когда вы вызываете super, чтобы найти «следующий» класс, который может быть базовым классом, или родным братом, или даже родственным подклассом. Вы можете найти статью Википедии о C3 linearization более доступной.

** Специально для встроенных классов, потому что они особенные в нескольких направлениях, которые не стоит вдаваться сюда. Опять же, многие встроенные классы фактически ничего не делают в своих __init__ и вместо этого выполняют всю инициализацию внутри конструктора __new__.

+0

Есть ли какой-либо выигрыш от Python, не вызывающий * all * '__init__'s, а только первый, который он находит? Я не вижу никого ... – Sventimir

+1

@Свентимир Да. Только для одного преимущества: представьте, что Lockable позже будет изменен, чтобы потребовать дополнительный аргумент, который dict не примет. Если Python автоматически вызывает каждый базовый класс, ваш код больше не сможет работать. Для получения дополнительной информации прочитайте ссылки, которые я разместил в сносках, и ответ на связанный с ним вопрос RvdK, связанный в комментарии к вашему вопросу. (Потому что это продолжение в основном является дублированием этого связанного вопроса.) – abarnert