Python中如何检测子类__init_subclass__等特殊方法是否被重写
问题解答
1. 检测技术失效的原因及其他失效场景
你所用的is身份检测逻辑,本质依赖「未重写的方法从父类继承时,访问得到的是同一个内存对象」的前提,但这个前提对类方法(@classmethod装饰的方法)完全不成立。
Python的类方法属于描述符类型,每次通过类访问类方法时,都会动态生成一个绑定了当前类的新方法对象,所以哪怕是同一个类连续两次访问同一个类方法,得到的对象身份也不一样:
>>> class A: ... @classmethod ... def test(cls): pass ... >>> A.test is A.test False
你遇到的两个特殊方法刚好都是Python隐式实现的类方法:
__init_subclass__是Python 3.6+新增的类生命周期钩子,默认隐式带有类方法语义,不需要显式加@classmethod装饰器__subclasshook__是Python抽象基类(abc模块)定义的钩子,要求必须是类方法才能生效
所以这两类方法访问时都会生成新的绑定对象,自然is检测会失效。除了这两个之外,所有你自己显式用@classmethod装饰的方法,都会导致这个检测逻辑失效。
注:你提供的原始检测函数存在变量名错误,
if not callable(method):中的method未定义,应该替换为subclass_method。
2. 替代检测方案
你可以避开getattr触发的描述符绑定逻辑,直接通过类的__dict__属性、遍历子类的方法解析顺序(MRO)来判断,不需要拿到绑定后的方法对象,只要判断方法的定义位置即可:
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`" ) # 遍历子类的MRO,找第一个定义目标方法的类 for cls in subclass.__mro__: if method_name in cls.__dict__: # 找到的定义类不是传入的父类,说明被重写 return cls is not superclass # 整个继承链都没有该方法,可根据业务需求调整返回值 return False
这个方案对普通方法、静态方法、类方法、特殊钩子方法都通用,不会触发描述符绑定逻辑,完全基于方法的定义位置判断,不受动态绑定的影响。如果仅需要判断是不是子类自身直接定义了目标方法,直接判断method_name in subclass.__dict__即可。
内容的提问来源于stack exchange,提问作者Alex Waygood
相关产品推荐
相关产品推荐

