为什么在这里发生递归?

时间:2012-08-04 20:52:46

标签: python class recursion decorator

最近我读了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)的{​​{1}}内使用__init__,我们会进入递归

我测试了,确实发生了递归。 但是,据我所知,MyClass继承了MyClass,因此object应仅仅是super(MyClass, self),但事实证明objectsuper(MyClass, self)

你能解释一下这里发生的事情,让我理解递归发生的原因吗?

1 个答案:

答案 0 :(得分: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__相同。但DecMyClassOrigMyClass的子类。关键点在于,因为MyClass引用了DecMyClassOrigMyClass实际上是在自己的子类上调用超级

  6. 因此super(DecMyClass, self).__init__再次调用OrigMyClass.__init__,再次调用自身,依此类推无限。

  7. 效果与此代码相同,这可能会使执行路径更加明显:

    >>> 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最终会再次调用自己的方法。

    编辑:只是为了澄清你提出的名字查询问题,这是另一个略有不同的例子,结果相同:

    >>> 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调用时。如此示例所示,在定义方法时,变量甚至不必存在;在您调用方法时查找该值。