Ale*_*ood 5 python oop inheritance
通常在Python中,可以使用以下技术来检测子类中的方法是否已被重写:
>>> class Foo:
... def mymethod(self): pass
...
>>> class Bar(Foo): pass
...
>>> Bar.mymethod is Foo.mymethod
True
Run Code Online (Sandbox Code Playgroud)
如果来自的方法尚未在 中被重写,则该表达式Bar.mymethod is Foo.mymethod
将计算为,但如果该方法已在 中被重写,则该表达式将计算为。此技术适用于从 继承的 dunder 方法以及非 dunder 方法:True
Foo
Bar
False
Bar
object
>>> Bar.__new__ is Foo.__new__
True
>>> Bar.__eq__ is Foo.__eq__
True
Run Code Online (Sandbox Code Playgroud)
我们可以在函数中形式化这个逻辑,如下所示:
def method_has_been_overridden(superclass, subclass, method_name):
"""
Return `True` if the method with the name `method_name`
has been overridden in the subclass
or an intermediate class in the method resolution order
"""
if not issubclass(subclass, superclass):
raise ValueError(
"This function only makes sense if `subclass` is a subclass of `superclass`"
)
subclass_method = getattr(subclass, method_name)
if not callable(method):
raise ValueError(f"'{subclass.__name__}.{method_name}' is not a method")
return subclass_method is not getattr(superclass, method_name, object())
Run Code Online (Sandbox Code Playgroud)
然而,当涉及到以下两种方法时,该技术就会失败:__init_subclass__
和__subclasshook__
:
>>> class Foo: pass
...
>>> class Bar(Foo): pass
...
>>> Bar.__init_subclass__ is Foo.__init_subclass__
False
>>> Bar.__subclasshook__ is Foo.__subclasshook__
False
Run Code Online (Sandbox Code Playgroud)
还有一个更令人困惑的例子:
>>> type.__init_subclass__ is type.__init_subclass__
False
Run Code Online (Sandbox Code Playgroud)
我有两个问题:
__init_subclass__
未定义__subclasshook__
后是否已在子类中定义?__init_subclass__
是一个特殊的方法,因为classmethod
即使您@classmethod
在定义它时不使用它来装饰它,它也是隐式的。然而,这里的问题并不是因为这__init_subclass__
是一种特殊方法。相反,您用来检测某个方法是否已在子类中被重写的技术存在一个根本错误:它根本不适用于任何classmethod
s:
>>> class Foo:
... def mymethod(self): pass
... @classmethod
... def my_classmethod(cls): pass
...
>>> class Bar(Foo): pass
...
>>> Bar.mymethod is Foo.mymethod
True
>>> Bar.my_classmethod is Foo.my_classmethod
False
Run Code Online (Sandbox Code Playgroud)
这是因为Python 中绑定方法的工作方式:Python 中的方法是描述符。
观察以下代码行与实例方法的等效性。第一种(更正常的)调用mymethod
实例的方法f
只是第二种调用实例方法的方法的语法糖f
:
>>> class Foo:
... def mymethod(self):
... print('Instance method')
...
>>> f = Foo()
>>> f.mymethod()
Instance method
>>> Foo.__dict__['mymethod'].__get__(f, Foo)()
Instance method
Run Code Online (Sandbox Code Playgroud)
每次调用__get__
in 中的未绑定方法都会生成一个新对象;Foo.__dict__
只有通过访问类上的实例方法,我们才能测试身份,就像您在问题中所做的那样。然而,对于s,即使从类classmethod
访问该方法也会调用该方法:__get__
>>> class Foo:
... @classmethod
... def my_classmethod(cls):
... print('Class method')
...
>>> Foo.my_classmethod is Foo.my_classmethod
False
>>> Foo.my_classmethod()
Class method
>>> Foo.__dict__['my_classmethod'].__get__(Foo, Foo)()
Class method
Run Code Online (Sandbox Code Playgroud)
关于什么__new__
?
您的问题指出您现有的方法适用于__new__
. 这很奇怪——我们刚刚确定这个方法不适用于classmethod
s,而且看起来__new__
确实像. 毕竟,第一个参数名为!然而,Python 文档明确表明事实并非如此:classmethod
__new__
cls
object.__new__(cls[, ...])
调用以创建 class 的新实例
cls
。__new__()
是一个静态方法(特殊情况,因此不需要这样声明),它将请求实例的类作为其第一个参数。
是一个staticmethod
,不是一个classmethod
!谜团已揭开。
检测子类是否覆盖超类方法的更好方法
确定某个方法是否已在子类中被重写的唯一可靠方法是按__dict__
方法解析顺序遍历每个类:
def method_has_been_overridden(superclass, subclass, method_name):
"""
Return `True` if the method with the name `method_name`
has been overridden in the subclass
or an intermediate class in the method resolution order
"""
if not issubclass(subclass, superclass):
raise ValueError(
"This function only makes sense if `subclass` is a subclass of `superclass`"
)
subclass_method = getattr(subclass, method_name)
if not callable(subclass_method):
raise ValueError(f"'{subclass.__name__}.{method_name}' is not a method")
for cls in subclass.__mro__:
if cls is superclass:
return False
if method_name in cls.__dict__:
return True
Run Code Online (Sandbox Code Playgroud)
此函数可以正确确定 或__init_subclass__
任何其他classmethod
, 是否已在子类中被重写:
>>> class Foo: pass
...
>>> class Bar(Foo): pass
...
>>> class Baz(Foo):
... def __init_subclass__(cls, *args, **kwargs):
... return super().__init_subclass__(*args, **kwargs)
>>> method_has_been_overridden(Foo, Bar, '__init_subclass__')
False
>>> method_has_been_overridden(Foo, Baz, '__init_subclass__')
True
Run Code Online (Sandbox Code Playgroud)
非常感谢@chepner和U12-Forward,他们的出色回答帮助我解决了这个问题。