【问题标题】:Is super() broken in Python-2.x? [closed]Python-2.x 中的 super() 是否损坏? [关闭]
【发布时间】:2011-02-21 13:07:06
【问题描述】:

经常说super 在 Python 2 中应该是 avoided。我在 Python 2 中使用 super 时发现,除非我提供所有参数,例如示例:

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

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

  • 除了2.7 is the last major release 到 Python 2 之外,为什么 super 在 Python 2 中仍然存在问题?
  • Python 3's super 如何以及为何发生了变化?有什么注意事项吗?
  • 以后我应该在什么时候以及为什么使用super

【问题讨论】:

  • 你没有给出任何你认为 super 被破坏的 why 的例子,除了它并不比显式调用基类更短,这既不是这里也不是那里...您显然不了解 super() 的用途(用于通过多重继承安全地向上调用)。
  • @fuzzyman:那是主观的,模糊不是我名字的一部分:)
  • 嗯,不。 “破碎”不是一个主观概念,在某些情况下需要。所以你没有解释为什么你认为 super 坏了(除了参考一篇没有这么说的文章),你也没有表现出任何理解 super 是 for 的样子(提示:它不等同于您展示的替代品)。
  • 当您说TheBaseClass.some_func(self, *args, **kwargs)super 更好时,您假设TheBaseClass 始终是ThisClass 的祖先,如果您有多重继承(或某人之后添加)。 super(ThisClass, self) 可能解析为 TheBaseClass 以外的其他内容。 super()。所以问题是你想打电话给链中的“下一个人”(最有可能)还是你想准确地打电话给TheBaseClass

标签: python python-3.x multiple-inheritance super python-2.x


【解决方案1】:

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() 调用之外,实际上没有办法使这项工作。

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

【讨论】:

  • B 的最后一行是不是意味着阅读super(B, self),那些defs 应该是classes?
  • 目前还不清楚子类化如何优于self._logger = Logger("B") - 在树中混合任意类从未奏效。你可以想一个类似的例子,但是直接调用。这些类需要设计成可以协同工作。如果不是,super 可以提供帮助。但是,正如文章所述,在调用超类时最好将参数作为关键字传递。如果Logger 类正确使用super,那么您的问题就不会存在。
  • @Rosh:如果我不使用super(),所有这些都可以正常工作。我根本不认为它坏了。我想在BLogged 实例b 上调用b.run_logged(value),而不是b._logger.run_logged(value)
  • 对于那些将来会遇到这个问题的人,Raymond Hettinger 给了a fantastic talk: Super is considered Super.
【解决方案2】:

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

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

  • 它不像听起来那样做。

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

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

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

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

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

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

嗯。好吗?

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

是的,你说过。

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

是的,你也这么说。

因此,有两个反对理由: 1. 名字不好。 2. 你必须始终如一地使用它。

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

【讨论】:

  • 我不明白你的意思。
  • 马特,试图侮辱回复你的人对你的技术论点没有任何帮助。
  • 仅供参考,我的个人资料阅读了弦理论研究论文并对其进行了批评。这是一个非常棒的个人资料。但我还是不明白你的意思。我的个人资料可能有,但是太忙了,无法回复我的电子邮件。
  • @Lennart Regebro:很抱歉造成混淆,“阅读”这个词更不寻常的用法:定义 6:en.wiktionary.org/wiki/read#Verb。我的意思是开玩笑,如果你的回答更具对话性,我会认为它是由 Java 粉丝写的。
  • 根据我的经验,Java 爱好者并不比 Pythonistas 更健谈...嗯。
【解决方案3】:

你似乎在你的帖子中暗示

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() 基本上是您的only hope 以获得正确的解决方案。在 Single-Inheritance 中,它只是比 SuperClass.foo(self, *args, **kwargs) 稍微冗长一些,并且没有什么不同。

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

【讨论】:

  • 我强烈不同意super() 是涉及多重继承时的“唯一希望”。有些类层次结构需要super(),但许多具有多重继承的类层次结构不需要。
  • @Sven super 是创建任何类似于通用解决方案(跨越不同类型的继承图)的唯一方法,据我所知。当然,如果您对通用解决方案不感兴趣,那么super 不会给您任何东西。另一方面,使用super 也不会丢失任何东西。所以,我会使用super
  • @Devin:你真的读过this page linked by the OP吗?至少你没有解决那里提到的任何问题。如果你使用super(),你正在失去一些东西。我曾经经常使用它,它破坏了我的很多代码。同时,每当我在代码中遇到super() 时,我都会将其编辑掉。
  • @Sven 这些年来我读过几次。我无法从略读中找到你在想什么。使用super 会失去什么,使用SuperClass.method(...) 会保留什么?我解决了问题中提到的问题,而不是文章,因为他实际上有问题。
  • @Sven:但这就是它的全部要点。在您不确切知道需要调用哪种方法的情况下使用super()。 :-) 如果你这样做了,你可以打电话给它。
【解决方案4】:

您是否阅读了链接它的文章?它并不认为应该避免使用super,但在使用它时应该警惕它的警告。文章总结了这些注意事项,但我不同意他们的建议。

本文的主要观点是多重继承可能会变得混乱,super 并没有像作者想要的那样有帮助。然而,在没有super 的情况下进行多重继承通常更加复杂。

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

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

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

【讨论】:

    【解决方案5】:

    @Sven Marnach:

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

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

    【讨论】:

    • 好吧,我知道为什么我的例子不起作用了。这是一个例子,经常使用super() 有缺点。您可以使示例与super() 一起工作,但它会变得非常丑陋,而对于显式的超类调用它是直截了当的。请注意,由于ALogger 都派生自object,因此涉及 菱形继承。同样,我的全部观点是 super() 不应被视为调用基类方法的标准方式。
    猜你喜欢
    • 1970-01-01
    • 2011-01-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-05
    • 1970-01-01
    • 2022-01-19
    • 1970-01-01
    相关资源
    最近更新 更多