45

人们经常说在 Python 2 中super应该避免使用。我super在 Python 2 中的使用中发现它永远不会按照我期望的方式运行,除非我提供所有参数,例如示例:

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

在我看来,这违背了使用的目的super(),它既不简洁,也不比TheBaseClass.some_func(self, *args, **kwargs). 对于大多数目的,方法解析顺序是一个遥远的童话。

4

5 回答 5

45

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对此无能为力。实际上没有办法使这项工作,除了删除super()调用B本身。

[另一个编辑:从这里和其他答案下方的所有评论来看,我似乎没有表达我的观点。以下是如何使用以下代码使此代码工作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()坏了。

于 2011-02-21T13:16:49.493 回答
33

super()在 Python 2 或 Python 3 中没有损坏。

让我们考虑一下博客文章中的论点:

  • 它不像听起来那样做。

好的,您可能同意或不同意,这是非常主观的。那它应该叫什么?super()是直接调用超类的替代品,所以这个名字对我来说似乎很好。它不会直接调用超类,因为如果这就是它所做的一切,那将毫无意义,因为无论如何你都可以这样做。好的,诚然,这可能并不明显,但是您需要super()的情况通常并不明显。如果你需要它,你正在做一些非常复杂的多重继承。这不会很明显。(或者你正在做一个简单的 mixin,在这种情况下,即使你没有阅读文档,它也会很明显并且行为符合你的预期)。

如果您可以直接调用超类,那可能就是您最终会做的事情。这是一种简单而直观的方法。super()只有当这不起作用时才会发挥作用。

  • 它与直接调用超类不太吻合。

是的,因为它旨在解决这样做的问题。当且仅当您确切知道它是什么类时,您才能直接调用超类。例如,对于 mixins,或者当您的类层次结构如此混乱以至于您实际上正在合并两个分支时(这是所有 using 示例中的典型示例super()),您就不会这样做。

因此,只要类层次结构中的每个类都有明确定义的位置,就可以直接调用超类。如果你不这样做,那么它就不起作用,在这种情况下你必须使用它super()。这就是super()它根据 MRO 计算出“下一个超类”是什么的关键,而无需您明确指定它,因为您不能总是这样做,因为您并不总是知道它是什么,例如,当使用混合。

  • 完全不同的编程语言 Dylan,一种 lisp-thingy,以另一种方式解决了这个问题,这是 Python 中无法使用的,因为它非常不同。

嗯。好的?

  • super()不调用你的超类。

是的,你这么说。

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

是的,你也这么说。

所以,有两个反对它的论点: 1. 名字不好。2. 你必须始终如一地使用它。

这并不意味着它被“破坏”或应该被“避免”。

于 2011-02-21T14:22:17.477 回答
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 的任何情况。它的行为只有在涉及 MI 时才“怪异”,而当涉及 MI 时,super()基本上是您获得正确解决方案的唯一希望。在 Single-Inheritance 中,它只是比 稍微冗长一些SuperClass.foo(self, *args, **kwargs),并且没有什么不同。

我认为我同意 Sven 的观点,即这种 MI 值得避免,但我不同意super值得避免。如果您的类应该被继承,则为您的类的super用户提供让 MI 工作的希望,如果他们以这种方式很奇怪,那么它会使您的类更有用。

于 2011-02-21T13:10:35.570 回答
3

您是否阅读了链接它的文章?它并没有得出super应该避免的结论,但是在使用它时你应该警惕它的警告。文章总结了这些警告,尽管我不同意他们的建议。

这篇文章的主要观点是多重继承会变得混乱,并且super没有作者想要的那么大的帮助。然而,不进行多重继承super通常更加复杂。

如果您不进行多重继承,super那么您的优势是任何从您的类继承的人都可以添加简单的 mixin,并且它们__init__将被正确调用。只需记住始终调用__init__超类的 ,即使您继承自object,并将所有剩余的参数(*a**kw)传递给它。当您从父类调用其他方法时,也使用super,但这次使用您已经知道的正确签名(即确保它们在所有类中具有相同的签名)。

如果你正在做多重继承,你必须更深入地挖掘,并且可能更仔细地重新阅读同一篇文章以了解警告。而且也只有在多重继承期间,您可能会出现对父级的显式调用可能比 更好super的情况,但是如果没有特定的场景,没有人可以告诉您是否super应该使用。

Python 3.x 中唯一的变化super是您不需要显式地将当前类传递self给它。这super更有吸引力,因为使用它意味着不会对父类或当前类进行硬编码。

于 2011-02-21T13:24:06.147 回答
1

@斯文马纳赫:

您的示例的问题是您将显式超类调用B.__init__和BLogger.__init__中的 Blogger 混合在一起super()。那是行不通的。要么使用所有显式超类调用,要么super()在所有类上使用。当你使用时,super()你需要在所有涉及的类上使用它,包括 AI think。同样在您的示例中,我认为您可以在所有类中使用显式超类调用,即A.__init__在 B类中使用。

当没有钻石继承时,我认为 super() 没有太多优势。但是,问题是,您事先不知道将来是否会继承任何钻石继承权,因此在这种情况下,super()无论如何使用它是明智的(但要始终如一地使用它)。否则,您最终将不得不在以后更改所有课程或遇到问题。

于 2011-02-21T16:08:57.340 回答