为什么在PySide/PyQt中经常使用super?

19

简短版(tl;dr)

我正在学习 PySide,大部分在线教程都使用super来初始化 UI 元素。这个重要吗(例如,更可扩展),还是只是个人喜好?

澄清:如我在详细版中所述,这不是另一个普通的线程询问何时使用 super(这已经被讨论过了)。相反,考虑到使用 super 而不是 <class>.__init__ 的 PySide 教程数量,我试图弄清楚在 PySide 应用程序中使用 super 是否标准?如果是这样的话,是因为需要调用 super 的情况(涉及解决继承)特别多,而这些情况在使用 PySide/PyQt 时经常出现的缘故?还是这只是个人喜好。

详细版

我刚接触 Python,并且正在使用 Zets 教程(http://zetcode.com/gui/pysidetutorial/firstprograms/)学习 PySide。教程的第二个例子包括:

from PySide import QtGui

class Example(QtGui.QWidget):
    def __init__(self):      
        super(Example, self).__init__()
        self.initUI()
    def initUI(self):
        self.setGeometry(300,300,250,150)
        self.setWindowTitle("PySide 101: Window the First!")
        self.show()

app=QtGui.QApplication(sys.argv)
ex=Example()
sys.exit(app.exec_())    

这段代码本来就能正常工作,但我从未使用过super。因此,我重写了上面的代码,成功地用更标准的显式调用父类替换了super

QtGui.QWidget.__init__(self)

我搜索了PySide教程(例如,http://qt-project.org/wiki/PySide-Newbie-Tutorials),发现它们全部都包含对super的调用。我的问题是:在PySide脚本中是否应该使用super

看起来super在具有继承菱形的情况下最为有用,因为它倾向于以合理的方式解析多重继承的实例。在PySide中经常使用super是因为这种菱形的情况很普遍,我将在更加复杂的实际例子中面临这种情况吗?[编辑:不需要,见下文]

为什么我要问这个问题?直接使用super不就好了吗?

我问这个问题是因为我学习Python的书籍(《Learning Python》by Lutz)在“super”这个主题上花费了超过20页,并明确告诫不要使用它。他建议新的Python用户在尝试使用super之前先走传统、明确的路线(例如,参见《Python基础教程》第5版的832页和1041-1064页)。他基本上将其描绘成一种不Pythonic、奥妙难解、很少实际需要的新风格,在刚开始时应该非常谨慎地对待它,并认为它被经验丰富的用户过度使用。

此外,查看两个基于PySide/PyQt的主要项目(Spyder和pyqtgraph)的源代码,均未使用super。其中一个项目(Spyder)明确告诉贡献者出于兼容性原因避免使用它(http://code.google.com/p/spyderlib/wiki/NoteForContributors)。

请注意,我在下面链接了一个密切相关的帖子,但那里的答案更普遍地讨论了何时需要使用super(当你有多重继承时)。我的问题是:在长期使用中,PySide脚本是否证明了或需要使用super,或者显式命名父类更符合Python的规范性和兼容性原因?还是只是个人口味问题?

如果它受到贬低(如我的初学者书籍所示),那么为什么在面向初学者的PySide教程中它如此普遍?如果有区别的话,似乎撰写这些教程的人都是经验丰富的Java程序员或为这样的程序员提供服务。但我不是。

相关主题

http://www.riverbankcomputing.com/pipermail/pyqt/2008-January/018320.html

Different ways of using __init__ for PyQt4

Understanding Python super() with __init__() methods</


2
个人而言,我使用<class>.__init(self,...),但最近我在原本子类化(假设)QWidget并覆盖了一堆方法时遇到了问题,在某些时候调用了父类方法。后来我将我的类更改为子类化QMainWindow,但忘记更改所有的QWidget.<method>调用。使用super()会更容易些。 - three_pineapples
@three_pineapples,所以这不是继承解决的问题,而主要是代码重构的问题?有趣。 - eric
不,这是为了继承解析(我会称之为继承解析)。在一个复杂的子类中,你不仅想调用parent_cls.__init__,还想在扩展功能时调用其他方法(例如,你的getText()实现可能想要调用parent_cls.getText())。使用super可以避免手动更新每个使用它的地方的parent_cls - three_pineapples
我指的是多重继承(包括钻石继承等)。 - eric
1
来自2017年的问候!现在的语法只需super().__init__(),甚至比BaseClass.__init__(self)还要短。 - Kos
2个回答

14

传统的实例化父类方法没有任何问题,还有一些优点。但是,使用super可以简化子类的创建和未来的代码修改,人们似乎已经将后者作为最重要的因素。这不仅适用于Pyside,也适用于Python中的面向对象编程(正如Kos的出色解答所指出的那样)。

简化代码修改的潜力在于,在PySide中需要根据其他QtGui对象(例如QtQui.QMainWindowQtGui.QWidget)定义子类。此外,人们倾向于对其父类进行修改,以便使用super似乎更容易,这样您就无需每次更改父类时都更新__init__方法。

因此,这不是使用super来解决多重继承的情况,大多数人认为它可能最适合的情况。相反,这是一个关于如果将来您的父类更改了,应该在__init__内做更少工作的问题。

以下是两位作者的回应,他们都写了我认为很好的PySide教程:

作者1:

我认为这是品味问题。早期的教程(PyQt和PySide)使用.init,后来我改成了super()。我个人更喜欢super()。

作者2:

人们使用super而不是.init(...)的原因是避免在更改父类时进行修改,例如如果您从QVBoxLayout切换到QHBoxLayout,则只需在类定义行中更改它,而不必在init方法中也更改。

所以,这些好处并不是特定于PySide的,而是更普遍地适用于编写子类/继承。

我不确定Lutz会怎么说,他似乎非常犹豫地认可super的使用(也许使用super违反了“明示优于隐式”的原则)。

四年后的更新
回想起来,这场辩论已经结束了,这个问题几乎有些古老了(这是我在SO上的第一个问题)。虽然以前有人争论使用super的问题,但这些争论已经结束了。特别是在Python 3中,super的便利性已经被证实,它只会使你的代码更容易维护。因为在Qt/Pyside/PyQt框架中,从更抽象的Qt类进行继承是普遍存在的,所以这不是一个小特性。当然,在你有复杂的继承关系时需要小心,但老实说自从我问过这个问题以来,我从未遇到过这个问题,而且我目前在我的所有代码中都使用super。这可能违反了“明示优于隐式”的原则,但“简单胜于复杂”和“实用性胜过纯洁性”是主导因素(这里的实际因素是“可维护性计数”)。


1
感谢您直接关注作者。这是非常有帮助的评论。 - kevinarpe

11

嗯,很好。但在我看来它只是与Qt/ PySide勉强相关。

首先,它们两者有何不同?如果您具有简单继承(也许不包括“混合”),则行为上没有区别。仍然存在一个视觉差异-您不需要再次命名基类-但是您必须命名相同的类。

当您具有多重继承时,差异就会开始了。然后,要为此层次结构调用一系列super():

          A
        /   \
       X     Y
        \   /
          Z

可以通过 super() 调用轻松进行如下操作:

<code>          A
            \
       X --- Y
        \   
          Z
</code>

不需要X和Y彼此了解即可实现。这与方法解析顺序的概念有关,该概念允许在Python文档中称为“协作多重继承”的编程风格。

如果有一个方法Foo,并且X和Y的实现建立在A的实现之上,则Z很容易依赖于X和Y,而它们甚至不需要知道彼此的存在。然而,这里有一个重要的前提条件:Foo在每个类中具有相同(或至少[兼容])的签名,由A的接口指定,它最初被定义。

__init__方法是特殊的:从技术上讲,它与super完全相同,但是!但是(更常见的情况)对于子类来说,它具有完全不同的签名。如果子类的__init__看起来不同,那么super将无法提供比显式基本调用更多的帮助,因为您无法使用协作式多任务处理。

注意:在这方面,Python非常不寻常:在大多数面向对象语言中,构造函数属于类,而不是实例;换句话说,大多数语言依赖于它们的__new__等价物,并且根本没有__init__

注2:我从未见过任何真正依赖协作式多重继承的代码。(对我来说,单继承已经足够复杂了;-))

此外,这里有一些很好的阅读资料:

[


1
点赞,因为对多重继承类型情况的解释很好。然而,我的问题的关键是与PySide的关系。在PySide编程中,这种情况是否经常发生,以至于应该这样做,并且这是否解释了为什么超级在初学者PySide教程中无处不在?也就是说,在PySide脚本的上下文中,这种情况是否经常出现,如果不使用super会很愚蠢?还是这是大多数教程奇怪地以Java为中心(其中super更自然)的副产品? - eric
2
我猜这个广泛使用的事实是因为一旦你决定使用super而不是<class>.__init__,保持一致性比在所有可能的变化之间切换更好。这显然可以通过你第一个相关主题中描述的“混淆”得到证明。 - sebastian
1
似乎这基本上是个口味问题,因此由于我害怕超级(来自Lutz的书),所以我将采用旧的方式。我找到了一个最近的教程,就是用那种方式实现的:http://www.pythoncentral.io/series/python-pyside-pyqt-tutorial/ - eric
1
对于__init__函数签名的评论,我给你加一分。如果签名发生更改,那么你必须使用<class>.__init__进行调用,以确保传递可接受的参数。另外,当我停止使用super时,我开始感到更加舒适使用OOP。Python的“Python之禅”中有这样一句话:“显式优于隐式”。我也是Lutz的粉丝。=) - Will Martin

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