6

最近我阅读了一篇关于如何在 Python 中制作单例的有趣讨论。其中一个解决方案是一个棘手的装饰器,在其代码中定义一个类作为装饰类的替代品

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)inside __init__of MyClass,我们就会进入递归

我测试了,确实发生了递归。但是,据我所知,MyClass继承object,所以super(MyClass, self)应该只是object,但事实证明super(MyClass, self)__main__.MyClass

您能否逐步解释一下这里发生了什么,以便我了解递归发生的原因?

4

1 回答 1

5

问题是,通过编写super(MyClass, self).__init__(text),您是说使用 super 相对于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引用DecMyClassOrigMyClass实际上是在自己的子类上调用super 。

  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. Subis的超类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(第一个参数,here someClass)的类参数在任何方面都不是特殊的。它只是一个普通的名字,在普通的时候,也就是在super调用执行的时候,以普通的方式查到它的值。如本例所示,在定义方法时变量甚至不必存在;在您调用该方法时会查找该值。

于 2012-08-04T21:08:20.277 回答