super().method()与super(self.__class__, self).method()的区别

10

这是我试图编写的代码:

class A(object):
    def bind_foo(self):
        old_foo = self.foo
        def new_foo():
            old_foo()
            #super().foo()
            super(self.__class__,self).foo()

        self.foo = new_foo

    def __init__(self):
        print("A __init__")          

    def foo(self):
        print("A foo")

class B(A):
    def __init__(self):
        print("B __init__")
        super().__init__()

    def foo(self):
        print("B foo")
        super().foo()

class C(A):
    def __init__(self):
        print("C __init__")
        super().__init__()
        super().bind_foo()

    def foo(self):
        print("C foo")    

b  = B()
b.foo()

c = C()
c.foo()

类B和A是期望的行为,也就是说,当我调用b.foo()时,它会同时调用a.foo()并使用super()。类C试图模仿子类B和父类A的行为,但这次我不想在子类中显式地放置super().foo(),但仍希望调用父类的foo()。它的工作方式符合预期。

然而,我不太明白的是,在A.bind_foo下,我必须使用super(self.__class__,self).foo()而不是super().foosuper().foo返回一个

"SystemError: super(): no arguments". 

有人能解释一下为什么会这样吗?


你在 bind_foo 中所做的是错误的。super(self.__class__, self).foo() 实际上是在实例的第一个超类上调用 foo。因此,如果你有 class B(A),然后 class C(B),然后 class D(C),并且执行 D().bind_foo(),你最终会调用 C 的实现。这不是你想要的。如果你想调用 D 的实现,只需调用 self.foo()。如果你想调用 A 的实现,请调用 A.foo(self) - abarnert
在这种情况下,理想情况是我希望D调用自己的实现,然后是C,然后是B,最后是A - No Harm In Trying
1个回答

31

在调用super()时,不应使用self.__class__type(self)

在Python 3中,在类B的方法内部调用没有参数的super()等效于super(B, self);请注意显式命名类。Python编译器会向使用没有参数的super()的方法添加一个__class__闭包单元(参见为什么Python 3.x的super()是魔法函数?),引用正在定义的当前类

如果您使用super(self.__class__, self)super(type(self), self),则当子类尝试调用该方法时,将出现无限递归异常;此时,self.__class__派生类,而不是原始类。请参见在派生类中调用super()时,我可以传递self.__class__吗? 因此,总结一下,在Python 3中:

class B(A):
    def __init__(self):
        print("B __init__")
        super().__init__()

    def foo(self):
        print("B foo")
        super().foo()

等于:

class B(A):
    def __init__(self):
        print("B __init__")
        super(B, self).__init__()

    def foo(self):
        print("B foo")
        super(B, self).foo()

但你应该使用前者,因为这样可以避免重复。

在Python 2中,你只能使用第二种形式。

对于你的bind_foo()方法,你必须传入一个明确的类来自其中搜索MRO,因为Python编译器无法确定在绑定新的替换foo时使用哪个类:

def bind_foo(self, klass=None):
    old_foo = self.foo
    if klass is None:
        klass = type(self)

    def new_foo():
        old_foo()
        super(klass, self).foo()

    self.foo = new_foo

你可以使用__class__(没有self)来让Python为你提供闭包单元,但这将是对A的引用,而不是C。当你绑定新的foo时,你希望在MRO中搜索被覆盖方法的起点从C开始。

请注意,如果现在创建一个从C继承的类D,事情将再次出错,因为现在你正在使用D而不是C作为起点调用bind_foo()并依次调用super()。那么你最好使用一个明确的类引用来调用bind_foo()。这里__class__(没有self.)就可以了:

class C(A):
    def __init__(self):
        print("C __init__")
        super().__init__()
        self.bind_foo(__class__)

现在你拥有了使用无参数的super()相同的行为,即将对super()传递一个引用到当前类(定义方法__init__所在的类),使得new_foo()表现得就像它是直接在C类定义中定义的一样。

请注意,在这里调用super()上的bind_foo()没有意义;你没有在这里重写它,所以你可以只调用self.bind_foo()


那么我应该如何解决手头遇到的C子问题呢? - No Harm In Trying
我怀疑在new_foo的定义中,无参数的super不起作用,因为它不是一个方法(只是将常规函数分配给实例变量)。将继承式方法重载(可以调用super)与实例变量重载混合在一起可能是一个坏主意。 - Blckknght
感谢您的帮助!假设我想让D继承自CC继承自BB继承自A;但我希望D的foo方法调用D的实现,然后是C的,再然后是B的,最后是A的。最好的方法是什么? - No Harm In Trying
通过为每个类提供一个正确的foo实现;调用super().foo()。另请参阅Raymond Hettinger关于super()用法的博客文章。这就是super()的作用,协同类设计。 - Martijn Pieters
每个 foo() 都需要显式地放置 super() 吗? - No Harm In Trying
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接