<blockquote>
<p>Should classes that inherit directly from <code>object</code> call <code>super().__init__()</code>?</p>
</blockquote>
<p>你似乎在为这个问题寻找一个简单的“是或否”答案,但不幸的是,答案是“视情况而定”。此外,在决定是否应该调用<code>super().__init__()</code>时,类是否直接从<code>object</code>继承有些无关紧要。不变的是,如果调用了<code>object.__init__</code>,那么应该不带参数地调用它,因为<code>object.__init__</code>不接受参数。在</p>
<p>实际上,在协作继承的情况下,这意味着您必须确保在调用<code>object.__init__</code>之前,所有参数都被<em>使用</em>。它并不意味着您应该避免调用<code>object.__init__</code>。<a href="https://github.com/requests/requests/blob/943a5c8e89db1758ae24adbbedacb3b05c32df4a/requests/exceptions.py#L19-L21" rel="noreferrer">Here</a>是在调用<code>super</code>之前使用参数的一个例子,<code>response</code>和{<cd9>}上下文已从可变映射^{<cd10>中弹出。在</p>
<p>我在前面提到过,一个类是否直接从<code>object</code>继承是一个红鲱鱼<sup>1</sup>。但是我还没有提到什么应该激励这个设计决策:如果你想让MRO继续搜索其他初始化器[read:other<code>anymethod</code>],那么应该调用super init[read:super<code>anymethod</code>]。如果您希望在此处停止MRO搜索,则不应调用super。在</p>
<p>如果<code>object.__init__</code>不起任何作用,为什么它会存在呢?因为它<em>做了一些事情:<em>确保调用它时没有参数</em>。参数的存在很可能表示bug<sup>2</sup>。<code>object</code>还可以用来停止超级调用链-<em>某人不必调用super,否则我们将无限递归。您可以通过不调用super直接自己停止它。如果不这样做,<code>object</code>将作为最后一个链接,并为您停止链。在</p>
<p>类MRO在编译时确定,通常是在定义类/导入模块时。但是,请注意,<code>super</code>的使用涉及到很多<em>运行时</em>分支的机会。你必须考虑:</p>
<ul>
<li>调用<code>super</code>方法时使用哪些参数(例如,您希望沿着MRO转发哪些参数)</li>
<li>是否调用super(有时您希望故意断开链)</li>
<li>创建<code>super</code><em>本身</em>的参数(如果有)(下面描述了一个高级用例)</li>
<li>在你自己的方法中,是在</em>之前还是在</em>之后调用一个代理方法(如果需要访问被代理方法设置的某些状态,请将super放在第一位;如果你正在设置代理方法依赖的某个状态,那么就把super放在最后一个位置——在某些情况下,你甚至想把super放在中间的某个地方!)在</li>
<li>这个问题主要是关于<code>__init__</code>,但是不要忘记super也可以与任何其他方法一起使用</li>
</ul>
<p>在极少数情况下,您可能会有条件地调用<code>super</code>调用。您可以检查您的<code>super()</code>实例是否具有this或that属性,并根据结果建立一些逻辑。或者,您可以调用<code>super(OtherClass, self)</code>显式地“跳过”一个链接,并手动遍历该部分的MRO。是的,如果默认行为不是你想要的,你可以劫持MRO!所有这些邪恶思想的共同点是对<a href="https://en.wikipedia.org/wiki/C3_linearization" rel="noreferrer">C3 linearization</a>算法的理解,Python如何生成MRO,以及super本身如何使用MRO。Python的实现或多或少是从<a href="https://en.wikipedia.org/wiki/Dylan_(programming_language)" rel="noreferrer">another programming language</a>中提取出来的,其中super被命名为<a href="https://opendylan.org/documentation/intro-dylan/objects.html" rel="noreferrer">^{<cd24>}</a>。老实说,<code>super</code>在Python中是一个非常糟糕的名字,因为它在初学者中造成了一个常见的误解,即您总是调用“最多”一个父类,我希望他们选择了一个更好的名称。在</p>
<p>在定义继承层次结构时,解释器无法知道您是否希望<em>重用某些其他类的现有功能,还是将其替换为替代实现,或者其他什么。任何一个决定都可能是一个有效和实用的设计。如果有一个关于何时和如何<code>super</code>的硬性规定当被调用时,它不会由程序员来选择——语言将从你的手中夺走决定权,只是自动地做正确的事情。我希望这能充分解释在<code>__init__</code>中调用super不是一个简单的yes/no问题。在</p>
<blockquote>
<p>If yes, how would you correctly initialize <code>SuperFoo</code>?</p>
</blockquote>
<p><sub>(问题<code>Foo</code>,<code>SuperFoo</code>等的来源)</sub></p>
<p>为了回答这一部分,我假设MCVE中显示的<code>__init__</code>方法实际上需要进行一些初始化(也许您可以在问题的MCVE代码中添加占位符注释)。根本不要定义<code>__init__</code>如果你所做的只是用相同的参数调用super,那就没有意义了。不要定义一个<code>__init__</code>,它只是<code>pass</code>,除非您有意在那里停止MRO遍历(在这种情况下,肯定需要注释!)。在</p>
<p>首先,在我们讨论<code>SuperFoo</code>之前,让我说{<cd35>}看起来是一个不完整或糟糕的设计。如何将<code>foo</code>参数传递给<code>Foo</code>的init?<code>3</code>的<code>foo</code>初始值是硬编码的。硬编码(或自动确定)foo的init值可能是可以的,但是<a href="https://en.wikipedia.org/wiki/Composition_over_inheritance" rel="noreferrer">you should probably be doing composition not inheritance</a>。在</p>
<p>至于<code>SuperFoo</code>,它继承了<code>SuperCls</code>和{<cd28>}。<code>SuperCls</code>看起来是为了继承,<code>Foo</code>不是。这意味着您可能有一些工作要做,正如<a href="https://fuhm.net/super-harmful/" rel="noreferrer">super harmful</a>所指出的那样。一种前进的方法,<a href="https://rhettinger.wordpress.com/2011/05/26/super-considered-super/" rel="noreferrer">as discussed in Raymond's blog</a>,是编写适配器。在</p>
<pre><code>class FooAdapter:
def __init__(self, **kwargs):
foo_arg = kwargs.pop('foo')
# can also use kwargs['foo'] if you want to leave the responsibility to remove 'foo' to someone else
# can also use kwargs.pop('foo', 'foo-default') if you want to make this an optional argument
# can also use kwargs.get('foo', 'foo-default') if you want both of the above
self._the_foo_instance = Foo(foo_arg)
super().__init__(**kwargs)
# add any methods, wrappers, or attribute access you need
@property
def foo():
# or however you choose to expose Foo functionality via the adapter
return self._the_foo_instance.foo
</code></pre>
<p>注意,<code>FooAdapter</code><em>有一个</em><code>Foo</code>,而不是{<cd45>}<em>是一个</em><code>Foo</code>。这不是唯一可能的设计选择。但是,如果您像<code>class FooParent(Foo)</code>那样继承,那么您就意味着<code>FooParent</code><em>是一个</em><code>Foo</code>,并且可以用于<code>Foo</code>的任何地方,使用组合通常更容易避免违反<a href="https://en.wikipedia.org/wiki/Liskov_substitution_principle" rel="noreferrer">LSP</a>。<code>SuperCls</code>还应通过允许{<cd54>}进行合作:</p>
^{pr2}$
<p>也许{<cd41>}也超出了你的控制范围,你也必须适应它,所以就这样吧。关键是,这是一种重用代码的方法,通过调整接口使签名匹配。假设每个人都很好地合作并消费他们所需要的,最终<code>super().__init__(**kwargs)</code>将代理<code>object.__init__(**{})</code>。在</p>
<blockquote>
<p>Since 99% of classes I've seen don't use <code>**kwargs</code> in their constructor, does that mean 99% of python classes are implemented incorrectly?</p>
</blockquote>
<p>不,因为<a href="https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it" rel="noreferrer">YAGNI</a>。99%的类在有用之前,是否需要立即支持100%的常规依赖注入?如果它们不坏的话,是不是坏了?作为一个例子,考虑集合文档中给出的<a href="https://docs.python.org/3/library/collections.html#ordereddict-examples-and-recipes" rel="noreferrer">^{<cd58>}</a>配方。<a href="https://github.com/python/cpython/blob/998b80636690ffbdb0a278810d9c031fad38631d/Lib/collections/__init__.py#L548" rel="noreferrer">^{<cd59>}</a>接受<code>*args</code>和{<cd54>},但是{a13}。如果你想使用这些参数中的一个,运气不好,你必须重写<code>__init__</code>并截获它们。<a href="https://github.com/python/cpython/blob/d13e59c1b512069d90efe7ee9b613d3913e79c56/Lib/collections/__init__.py#L96" rel="noreferrer">^{<cd63>}</a>根本没有协同定义,实际上,一些<a href="https://github.com/python/cpython/blob/d13e59c1b512069d90efe7ee9b613d3913e79c56/Lib/collections/__init__.py#L116" rel="noreferrer">parent calls are hardcoded</a>到{<cd64>}-并且任何下一行的<code>__init__</code>都不会被调用,因此任何MRO遍历都将在其轨道上停止。如果您意外地将其定义为<code>OrderedCounter(OrderedDict, Counter)</code>而不是<code>OrderedCounter(Counter, OrderedDict)</code>,元类基仍然能够创建一致的MRO,但是该类根本不能作为有序计数器工作。在</p>
<p>尽管有这些缺点,但是<code>OrderedCounter</code>配方还是像宣传的那样工作,因为MRO是为预期的用例设计的。因此,为了实现依赖注入,您甚至不需要100%正确地进行协作继承。这个故事的寓意是完美是进步的敌人。如果你想把<code>MyWhateverClass</code>塞进你能想象得到的任何疯狂的继承树中,那就继续吧,但这是由你自己来编写必要的脚手架来实现的。与往常一样,Python不会阻止您以任何足够好的方法来实现它。在</p>
<p><sup>1</sup><sub>无论是否在类声明中编写,都始终从object继承。为了与2.7运行时交叉兼容,许多开源代码库都会显式地从object继承。</sub></p>
<p><sup>2</sup><sub>这一点将与sub一起进行更详细的解释CPython源<a href="https://github.com/python/cpython/blob/24bd50bdcc97d65130c07d6cd26085fd06c3e972/Objects/typeobject.c#L3598-L3638" rel="noreferrer">here</a>中<code>__new__</code>和{<cd20>}之间的关系。</sub></p>