【问题标题】:How should callables retrieved with super() be called? [duplicate]应该如何调用使用 super() 检索的可调用对象? [复制]
【发布时间】:2021-07-29 00:10:24
【问题描述】:

我注意到通过这个B.f 实现,调用B.f(B) 引发TypeError

>>> class A:
...     def f(self): print('foo')
... 
>>> class B(A):
...     def f(self):
...         super().f()
...         print('bar')
... 
>>> B.f(B())  # instance self argument
foo
bar
>>> B.f(B)    # non-instance self argument
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in f
TypeError: f() missing 1 required positional argument: 'self'

但是有了这个B.f 实现,调用B.f(B) 就可以了:

>>> class A:
...     def f(self): print('foo')
... 
>>> class B(A):
...     def f(self):
...         if isinstance(self, B): super().f()  # bound method call
...         else: super().f(self)                # function call
...         print('bar')
... 
>>> B.f(B())  # instance self argument
foo
bar
>>> B.f(B)    # non-instance self argument
foo
bar

这是因为super().f(即super(B, self).f)在selfB 的实例时检索绑定方法 types.MethodType(A.f, self),并检索函数 em> A.f 否则:

>>> super(B, B()).f
<bound method A.f of <__main__.B object at 0x10e6bda90>>
>>> super(B, B).f
<function A.f at 0x10e7d6790>

所以我想知道上面的两个B.f 实现中哪一个是惯用的。换句话说,类设计者是否应该假设类的函数将始终使用作为类实例的self 参数调用(如第一个B.f 实现总是调用super().f 作为绑定方法, 即super().f()),或者他是否也应该处理self 参数不是类* 实例的情况(如第二个B.f 实现,它调用super().f 作为绑定方法,即super().f() , 作为函数,即super().f(self))?


* 自 Python 3.0 起允许高级用法,这是可能的,如 Guido van Rossum explained:

在 Python 3000 中,未绑定方法的概念已被移除,表达式“A.spam”返回一个普通的函数对象。事实证明,第一个参数必须是 A 的实例的限制对诊断问题几乎没有帮助,并且经常成为高级用法的障碍——有些人称之为“鸭子打字自我”,这似乎是一个合适的名称。

【问题讨论】:

  • 正如我在第二个链接的回答中所说:super() 的单参数形式不适用于重要的极端情况,它是在 Python 2 世界中设计的并且已过时。 Guido 同意它应该被视为已弃用。使用这种形式并不习惯。
  • @MartijnPieters 我认为存在混淆。这篇文章实际上根本不是关于super() 的单参数形式(它甚至不是特定于super(),参见下面的my answer)。 Guido指的是鸭子类型self的应用(self理解为类方法的第一个参数,而不是内置函数的第二个参数super()——我认为这是你感到困惑的地方)。
  • @MartijnPieters 所以既然它不是重复的,除非我弄错了,我们可以重新打开这个问题吗?
  • @MartijnPieters 我已经打开了一个related question 关于方法调用可能失败的另一种方式。

标签: python function methods super


【解决方案1】:

我想知道上面的两个 B.f 实现中哪个是惯用的

更简单的是惯用的。

我真的看不出为什么子类会执行 isinstance(self, Subclass) 或调用者想要执行 B.f(B) 的充分理由。

【讨论】:

  • 和你一样,我想知道 Guido 所指的这些“高级用法”是什么。
【解决方案2】:

通常,有实例方法(往往有self 作为第一个参数,没有装饰器)、静态方法(有@staticmethod 装饰器)和类方法(有@classmethod 装饰器)。你会看到这两个装饰器(staticmethod、classmethod)被列为built-in functions

当您调用B().f() 时,由于B() 是一个实例,因此方法会隐式转换为一个函数,其第一个参数是实例,即is described in the docs here

另一方面,当您致电B.f() 时,这不会发生;没有插入额外的参数值(same docs 也解释了这一点)。但是,由于方法 f 是使用一个没有默认值的参数定义的,因此会出现错误“缺少 1 个必需的位置参数...”。

至于代码约定,您可以添加isinstance 检查,但通常情况下,代码会假定用户将使用正确类型或从正确类型的实例调用方法,如果必要的;否则用户会遇到错误。这样,您的代码就可以保持简单。

b = B()
# The `self` identifier inside `B.f` will be identical in the following two calls
b.f()
B.f(b)  # Would not expect users to call `f` this way unless marked as static

# The `self` identifier in `B.f` below is a class rather than an instance of the class, as in the above two cases
# Typically, you would _not_ expect users to call `f` this way.
B.f(B)

【讨论】:

  • 我想知道这些“高级用法”Guido指的是什么。
【解决方案3】:

我刚刚意识到这个A.g 实现的问题并不特定于使用super()

>>> class A:
...     def f(self): print('foo')
...     def g(self):
...         self.f()
...         print('bar')
... 
>>> A.g(A())
foo
bar
>>> A.g(A)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in g
TypeError: f() missing 1 required positional argument: 'self'

我们通常不会编写这个 A.g 实现:

>>> class A:
...     def f(self): print('foo')
...     def g(self):
...         if isinstance(self, A): self.f()  # bound method call
...         else: self.f(self)                # function call
...         print('bar')
... 
>>> A.g(A())
foo
bar
>>> A.g(A)
foo
bar

但我们应该这样做吗?我想知道 Guido van Rossum 所指的非实例第一参数的这些“高级用法”是什么。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-01-11
    • 2014-08-10
    • 1970-01-01
    • 2010-10-07
    • 1970-01-01
    • 1970-01-01
    • 2020-04-06
    • 2017-02-11
    相关资源
    最近更新 更多