2012-08-04 2 views
5

Недавно я прочитал an interesting discussion on how to make a singleton in Python. Одним из решений было tricky decorator defining a class inside its code as a substitute for decorated class:Почему здесь происходит рекурсия?

def singleton(class_): 
    class class_w(class_): 
     _instance = None 
     def __new__(class2, *args, **kwargs): 
      if class_w._instance is None: 
       class_w._instance = super(class_w, class2).__new__(class2, *args, **kwargs) 
       class_w._instance._sealed = False 
      return class_w._instance 
     def __init__(self, *args, **kwargs): 
      if self._sealed: 
       return 
      super(class_w, self).__init__(*args, **kwargs) 
      self._sealed = True 
    class_w.__name__ = class_.__name__ 
    return class_w 

@singleton 
class MyClass(object): 
    def __init__(self, text): 
     print text 
    @classmethod 
    def name(class_): 
     print class_.__name__ 

x = MyClass(111) 
x.name() 
y = MyClass(222) 
print id(x) == id(y) 

Выход:

111  # the __init__ is called only on the 1st time 
MyClass # the __name__ is preserved 
True # this is actually the same instance 

Утверждается, что если мы используем super(MyClass, self).__init__(text) внутри __init__ из MyClass, мы получаем в рекурсии.

Я тестировал и действительно рекурсия случается. Но, как я понимаю, MyClass наследует object, так super(MyClass, self) должны просто быть просто object, но оказывается, что super(MyClass, self) является __main__.MyClass

Не могли бы вы объяснить, что здесь происходит шаг за шагом для меня, чтобы понять причины, почему рекурсии происходит ?

+1

Super() немного сложнее с python, проверьте http://rhettinger.wordpress.com/2011/05/26/super-considered-super/ – Lycha

ответ

5

Проблема заключается в том, что, написав super(MyClass, self).__init__(text), вы говорите, что используете суперсоответствие по отношению к любому классу MyClass, к которому относится в то время, когда вызывается super. Но декоратор заменяет MyClass подклассом самого себя. Поэтому, когда ваш оригинальный метод __init__ называется MyClass, фактически относится к подклассу класса, который определяет метод выполнения.

Чтобы сказать это шаг за шагом, я собираюсь назвать исходный класс (как указано в источнике) OrigMyClass, а полученную версию (после декоратора) DecMyClass. Я буду использовать MyClass как переменную, потому что ее значение изменяется во время выполнения.

  1. Вы определить __init__ метод на OrigMyClass, но __init__ метод вызывает super(MyClass, self), не super(OrigMyClass, self). Таким образом, какой метод будет фактически называться, зависит от того, что MyClass относится к во время вызова метода. Значение MyClass проверяется во время выполнения, как и любая другая переменная; размещение его внутри вызова super или внутри метода __init__ не магически привязывает его к классу, в котором он находится, когда вы его пишете; переменные в функциях вычисляются, когда они вызывается, а не когда они определены.

  2. Декоратор работает. Декоратор определяет новый класс DecMyClass как подкласс OrigMyClass. DecMyClass определяет __init__, который вызывает super(DecMyClass, self).

  3. После запуска декоратора имя MyClass связано с классом DecMyClass. Обратите внимание, что это означает, что когда вызов super(MyClass, self) будет выполнен позже, он будет делать super(DecMyClass, self).

  4. Когда вы делаете MyClass(111), вы создаете объект DecMyClass. DecMyClass.__init__ звонки super(DecMyClass, self).__init__. Это выполняет OrigMyClass.__init__.

  5. OrigMyClass.__init__ звонки super(MyClass, self).__init__.Потому что MyClass относится к DecMyClass, это то же самое, что и super(DecMyClass, self).__init__. Но DecMyClass является подклассом OrigMyClass. Ключевым моментом является то, что, поскольку MyClass относится к DecMyClass, OrigMyClass на самом деле вызывает супер по подклассу самого себя.

  6. Таким образом, super(DecMyClass, self).__init__ снова вызывает OrigMyClass.__init__, который снова называет себя и так далее до бесконечности.

Эффект такой же, как этот код, который может сделать путь выполнения более очевидным:

>>> class Super(object): 
...  def __init__(self): 
...   print "In super init" 
...   super(Sub, self).__init__() 
>>> class Sub(Super): 
...  def __init__(self): 
...   print "In sub init" 
...   super(Sub, self).__init__() 

Обратите внимание, что Super называет super(Sub, self). Он пытается вызвать метод суперкласса, но он пытается вызвать метод суперкласса Sub. Суперкласс Sub - Super, поэтому Super снова начинает свой собственный метод.

Edit: Только для уточнения вопросов имя-подстановок вы подняли, вот еще немного другой пример, который имеет тот же результат:

>>> class Super(object): 
...  def __init__(self): 
...   print "In super init" 
...   super(someClass, self).__init__() 
>>> class Sub(Super): 
...  def __init__(self): 
...   print "In sub init" 
...   super(Sub, self).__init__() 
>>> someClass = Sub 

Это должно дать понять, что класс аргумент super (первый аргумент , здесь someClass) не является особенным. Это просто обычное имя, значение которого просматривается обычным способом в обычное время, а именно при выполнении вызова super. Как показано в этом примере, переменная даже не должна существовать во время определения метода; значение отображается в то время, когда вы звоните метод.

+0

Oh. Это было трудно понять. Но мне кажется, что я понял! Главное, что я не понимал, было то, что «super (MyClass, self)» не всегда ссылается на «OrigMyClass», но в какой-то момент (после отделочных работ) начинает ссылаться на «DecMyClass». И в итоге я понял, что '__init__' просто ищет лисичные области, пока не найдет« MyClass »в области модуля, где он относится к« DecMyClass ». До этого понимания я думал, что '__init__' живет внутри' MyClass', поэтому он всегда должен быть связан с начальным классом 'OrigMyClass'. Теперь все ясно! Спасибо! – ovgolovin

+0

@ovgolovin: Да, но ничего не стоит, это то же самое, что * всегда * происходит. Любая ссылка на любое имя в любой функции всегда просматривается таким образом (если только имя не назначено). Это не что-то особенное для «супер». – BrenBarn

+0

@ovgolovin: Я добавил еще один маленький пример, чтобы прояснить это. – BrenBarn

Смежные вопросы