Python-2.x中的super()是否存在问题?

48 投票
5 回答
61962 浏览
提问于 2025-04-16 12:13

很多人说在Python 2中应该避免使用super。我在使用Python 2的super时发现,它的表现总是和我预期的不一样,除非我提供所有的参数,比如下面这个例子:

super(ThisClass, self).some_func(*args, **kwargs)

在我看来,这样做就失去了使用super()的意义,因为它既不简洁,也没有比直接调用TheBaseClass.some_func(self, *args, **kwargs)好多少。对于大多数情况来说,方法解析顺序就像一个遥不可及的童话。

  • 除了2.7是Python 2的最后一个主要版本这一点,为什么在Python 2中super依然有问题呢?
  • Python 3中的super是如何变化的?有什么需要注意的地方吗?
  • 未来我什么时候以及为什么应该使用super呢?

5 个回答

6

你在帖子里似乎暗示

def some_func(self, *args, **kwargs):
    self.__class__.some_func(self, *args, **kwargs)

并不是无限递归。其实它是的,使用super会更准确。

另外,是的,你需要把所有参数都传给super()。这有点像抱怨max()不按预期工作,除非你把所有想要检查的数字都传给它。

不过在3.x版本中,所需的参数更少了:你可以用super().foo(*args, **kwargs)来代替super(ThisClass, self).foo(*args, **kwargs)


总之,我不太确定在什么情况下应该避免使用super。它的行为只有在涉及多重继承时才会显得“奇怪”,而在多重继承的情况下,super()基本上是你获得正确解决方案的唯一希望。在单继承中,它只是比SuperClass.foo(self, *args, **kwargs)稍微多一些文字,但没有其他不同。

我觉得我同意Sven的看法,认为这种多重继承是值得避免的,但我不同意说super值得避免。如果你的类是为了被继承而设计的,super给使用你类的用户提供了希望,让他们在需要多重继承时能够正常工作,这样会让你的类更好用。

33

super() 在 Python 2 和 Python 3 中并没有问题。

我们来看看这篇博客中的观点:

  • 它的功能和听起来的不一样。

好吧,这个观点你可能同意也可能不同意,毕竟这是主观的。那么它应该叫什么呢?super() 是用来替代直接调用父类的,所以这个名字我觉得还不错。它并不是直接调用父类,因为如果只是这样做,那就没什么意义了,因为你本来就可以直接调用。确实,这一点可能不太明显,但需要用到 super() 的情况通常也不容易发现。如果你需要它,那你可能正在处理一些复杂的多重继承。这种情况不会很明显。(或者你在做简单的混合类,这种情况下就会很明显,并且即使你没看文档,它的表现也会如你所期待的那样。)

如果你可以直接调用父类,那你可能最终会选择这样做。这是最简单、最直观的方式。super() 只有在直接调用不奏效时才会派上用场。

  • 它和直接调用父类不太兼容。

是的,因为它是为了解决直接调用父类时遇到的问题而设计的。你只有在确切知道父类是什么的情况下,才能直接调用父类。比如在使用混合类时,你并不知道父类是什么,或者当你的类层次结构非常混乱,以至于你实际上是在合并两个分支时(这也是使用 super() 的典型例子)。

所以,只要你的类层次结构中的每个类都有明确的位置,直接调用父类是可行的。如果没有,那就不行,这时候你必须使用 super()super() 的作用就是根据方法解析顺序(MRO)来找出“下一个父类”,而不需要你明确指定,因为你并不总是知道它是什么,尤其是在使用混合类时。

  • 完全不同的编程语言 Dylan,类似于一种 Lisp 的东西,以另一种方式解决了这个问题,但这种方式在 Python 中无法使用,因为它们非常不同。

嗯,好吧?

  • super() 并不直接调用你的父类。

是的,你已经说过了。

  • 不要混用 super() 和直接调用。

是的,你也说过了。

所以,有两个反对的观点:1. 名字不好。2. 你必须一致使用。

这并不意味着它是“坏的”或者应该“避免使用”。

46

super() 这个东西并没有坏掉——它只是不能被当作调用基类方法的标准方式。这个在 Python 3.x 中并没有改变。唯一的变化是,在标准情况下,你不需要传递参数 self, cls,因为 self 是当前函数的第一个参数,而 cls 是当前定义的类。

关于你问的什么时候实际使用 super(),我的回答是:几乎不需要。我个人尽量避免那种需要用到 super() 的多重继承。

补充说明:我曾经遇到过一个现实生活中的例子:我有一些类定义了一个 run() 方法,其中一些类有基类。我使用 super() 来调用继承的构造函数——我觉得这没什么,因为我只用了单一继承:

class A(object):
    def __init__(self, i):
        self.i = i
    def run(self, value):
        return self.i * value

class B(A):
    def __init__(self, i, j):
        super(B, self).__init__(i)
        self.j = j
    def run(self, value):
        return super(B, self).run(value) + self.j

想象一下,有好几个这样的类,它们都有各自的构造函数原型,并且都有相同的 run() 接口。

现在我想给这些类添加一些额外的功能,比如日志记录。这个额外的功能需要在所有这些类上定义一个额外的方法,比如 info()。我不想去改动原来的类,而是想定义一组新的类,继承自原来的类,添加 info() 方法,并继承一个提供实际日志记录的混合类。现在,我就不能再在构造函数中使用 super() 了,所以我用了直接调用:

class Logger(object):
    def __init__(self, name):
        self.name = name
    def run_logged(self, value):
        print "Running", self.name, "with info", self.info()
        return self.run(value)

class BLogged(B, Logger):
    def __init__(self, i, j):
        B.__init__(self, i, j)
        Logger.__init__("B")
    def info(self):
        return 42

这时候事情就不再正常工作了。在基类构造函数中的 super() 调用突然调用了 Logger.__init__(),而 BLogged 无法对此做任何处理。实际上,除了去掉 B 中的 super() 调用,别无他法。

[另一个补充说明:根据这里和其他答案下的评论来看,我似乎没有表达清楚我的观点。以下是如何使用 super() 让这段代码正常工作的:

class A(object):
    def __init__(self, i, **kwargs):
        super(A, self).__init__(**kwargs)
        self.i = i
    def run(self, value):
        return self.i * value

class B(A):
    def __init__(self, j, **kwargs):
        super(B, self).__init__(**kwargs)
        self.j = j
    def run(self, value):
        return super(B, self).run(value) + self.j

class Logger(object):
    def __init__(self, name, **kwargs):
        super(Logger,self).__init__(**kwargs)
        self.name = name
    def run_logged(self, value):
        print "Running", self.name, "with info", self.info()
        return self.run(value)

class BLogged(B, Logger):
    def __init__(self, **kwargs):
        super(BLogged, self).__init__(name="B", **kwargs)
    def info(self):
        return 42

b = BLogged(i=3, j=4)

把这个和显式调用父类的方式进行比较。你可以选择你更喜欢哪个版本。

这些故事和类似的情况就是我认为 super() 不应该被视为调用基类方法的标准方式 的原因。这并不意味着 super() 是坏的。

撰写回答