Python如何覆写类方法形式数值魔法方法 正确响应类+运算符
C + 3的具体求值逻辑 你观察到的差异来自Python数据模型对特殊方法(双下划线魔法方法)的固定查找规则,和@classmethod装饰器本身没有关系:
- 对任意对象执行运算符操作时,Python不会走正常的实例属性查找流程,而是直接在该对象的类型上找对应的特殊方法。
- 类本身也是对象,它的类型就是定义它时使用的元类。所以执行
C + 3时,实际执行的等价代码是type(C).__add__(C, 3),也就是直接去元类Meta上找__add__,完全不会检查C自身的属性字典里有没有定义__add__。 - 而你写
C.__add__(3)是普通的显式属性访问,走正常的getattr查找流程:先查C自身的属性,找到被@classmethod装饰的__add__就绑定后调用,所以会输出C。
你写的两段测试代码也完全符合这个规则:
- 普通方法
call()不是双下划线特殊方法,C.call()走正常属性查找,会优先拿到类自己定义的类方法版本,输出C。 - 没有自定义元类时,
C的类型是内置type,type没有实现__add__,所以C + 3直接抛出TypeError。
不派生新元类的覆写方案
不需要为每个要自定义__add__的类单独写新元类,有两种通用实现方式:
方案1:在公共元类中增加分发逻辑
修改公共元类的__add__实现,调用前先检查当前类是否自定义了__add__方法,如果有就优先调用类自己的实现,否则走元类默认逻辑:
from abc import ABCMeta class Meta(ABCMeta): def __add__(cls, other): # 检查类自身是否定义了__add__ if '__add__' in cls.__dict__: method = cls.__dict__['__add__'] # 手动绑定类方法/普通方法的第一个参数 return method.__get__(cls, type(cls))(other) # 默认元类逻辑 print('meta __add__') class C(metaclass=Meta): @classmethod def __add__(cls, other): print('C __add__') C + 3 # 输出 C __add__
方案2:通过__init_subclass__动态生成适配元类
如果不想修改原有公共元类的代码,可以在基类中借助__init_subclass__钩子,检测到子类自定义__add__时,动态生成专属元类替换子类的类型,不需要手动编写元类代码:
from abc import ABCMeta class Meta(ABCMeta): def __add__(cls, other): print('meta __add__') class HookBase(metaclass=Meta): def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) if '__add__' in cls.__dict__: # 动态创建继承自原有元类的新元类 custom_meta = type( f'{cls.__name__}Meta', (type(cls),), { '__add__': lambda meta_cls, other: cls.__dict__['__add__'].__get__(meta_cls, type(meta_cls))(other) } ) # 替换子类的类型为新元类的实例 cls.__class__ = custom_meta class C(HookBase): @classmethod def __add__(cls, other): print('C __add__') C + 3 # 输出 C __add__
注意:直接给类对象赋值
__add__方法(比如C.__add__ = classmethod(...))只会对显式调用C.__add__()生效,只要元类层面没有做上述分发处理,运算符调用永远不会访问到类自身定义的__add__。
内容的提问来源于stack exchange,提问作者D-Y
相关产品推荐
相关产品推荐

