【问题标题】:Can we use super() to test identity between class methods in the MRO?我们可以使用 super() 来测试 MRO 中类方法之间的身份吗?
【发布时间】:2020-06-10 10:41:26
【问题描述】:

考虑以下示例:

class A:
 def m():
  pass

class B(A):
 pass

以及以下终端输出:

>>> b = B()
>>> b.m
<bound method A.m of <__main__.B object at 0x000001EFFF24C748>>
>>> super(b.__class__, b).m
<bound method A.m of <__main__.B object at 0x000001EFFF24C748>>
>>> b.m is super(b.__class__, b).m
False
>>> b.m == super(b.__class__, b).m
True

为什么它们相等但不相同?继承时是否复制了该方法?

有没有更好的方法来测试子类是否覆盖了父方法?

【问题讨论】:

  • stackoverflow.com/questions/9436681/… 也许这对你有帮助
  • 我认为在将方法绑定到实例时,幕后会发生一些魔法(可能是描述符)。这就是为什么A.m is B.m 为真,而B.m is b.m 为假。
  • @AlexanderLekontsev 那里的大多数分析者似乎都在寻求更多需要干预类的迂回解决方案。
  • @myrmica 是对的! super() 返回它在 MRO 列表中找到的第一个方法的绑定版本。每个函数都是一个描述符,为了获得函数的绑定版本,描述符被调用。每次调用描述符时,它都会产生一个不同的函数绑定实例!

标签: python inheritance


【解决方案1】:

您可以使用__dict__ 属性来检查哪些方法和属性已被覆盖:

>>> class A:
...     def m():
...         pass
...
>>> class B(A):
...     pass
...
>>> class C(A):
...     def m():
...         pass
...
>>> 'm' in A.__dict__
True
>>> 'm' in B.__dict__
False                    # not overridden
>>> 'm' in C.__dict__
True                     # overridden

【讨论】:

  • 其实你让我走上了解决这个问题的正确道路:B.m.__get__(b) is B.m.__get__(b) 将产生False,所以我应该测试类的未绑定版本之间的身份。
  • 所以b.__class__.m is super(b.__class__, b).m.__func__ 将是我正在寻找的解决方案。我希望你不介意我也在写我的答案。遍历 MRO 对我的用例来说真的很关键,因为我有复杂和动态的继承和组合
【解决方案2】:

使用super(b.__class__, b) 生成一个实现__getattr__ 方法的对象,该方法将从位置1 开始沿__mro__ 属性向上(跳过当前类)并查找具有指定属性的第一个类。然后它将返回该绑定方法。如需更好的解释,请参阅this answer

知道所有函数也是下面的描述符

class A:
 def m(self):
  pass

创建一个对象A,其属性为m,它将是一个函数和描述符。当您初始化类 A 的对象 a 时,它基本上会导致 a.m = A.m.__get__(a) 生成以 a 作为第一个参数 self 的绑定方法。

现在,由于 super 还检索绑定方法,因此要检查的是产生终端输出的 A.m.__get__(a) 的 2 个实例之间的身份:

>>> A.m.__get__(a)
<bound method A.m of <__main__.A object at 0x...>>
>>> A.m.__get__(a) is A.m.__get__(a)
False

所以对类描述符m 的两次调用会产生不同的绑定实例,这就是身份检查失败的原因。相反,您应该测试生成绑定方法的函数的身份。幸运的是,绑定方法包含返回原始函数的__func__ 属性。因此,要查找任何实例的类是否覆盖了继承的函数,而不仅仅知道函数的实例和名称,您可以这样做:

>>> a.__class__.m is super(a.__class__, a).m.__func__
True

【讨论】:

  • 如果名称也是动态的,您可以将其更改为getattr(a.__class__, name) is getattr(super(a.__class__, a), name).__func__
猜你喜欢
  • 2013-05-13
  • 1970-01-01
  • 2015-03-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-28
  • 1970-01-01
相关资源
最近更新 更多