重定义Rational后isinstance检测报错?提前调用却正常的原因
Python 3.10.0中numbers.Rational检测的TypeError问题分析
问题重现
失败场景
import numbers class RationalMeta(type, numbers.Rational): pass class Rational(metaclass=RationalMeta): pass print(isinstance(1, numbers.Rational)) # 输出: True print(isinstance(1., numbers.Rational)) # 报错: TypeError: unbound method type.__subclasses__() needs an argument
成功场景
import numbers print(isinstance(1., numbers.Rational)) # 输出: False class RationalMeta(type, numbers.Rational): pass class Rational(metaclass=RationalMeta): pass print(isinstance(1, numbers.Rational)) # 输出: True print(isinstance(1., numbers.Rational)) # 输出: False
两者唯一区别是成功场景在自定义类前提前执行了一次isinstance(1., numbers.Rational)检测。
原因解析
这个问题根源在于抽象基类(ABC)的缓存机制和元类继承冲突:
元类继承的冲突
numbers.Rational是基于abc.ABCMeta的抽象基类,而你定义的RationalMeta同时继承了type和numbers.Rational。这种继承方式会导致元类的方法解析顺序(MRO)混乱:当后续执行isinstance检测时,ABC的子类遍历逻辑会错误地将RationalMeta当作numbers.Rational的子类,进而调用未绑定的type.__subclasses__方法——该方法需要绑定到具体类实例才能执行,因此抛出TypeError。ABC缓存的预初始化
当你提前调用一次isinstance(1., numbers.Rational)时,ABC机制会自动执行_abc_init方法,初始化三个关键缓存:__abc_cache__: 存储已确认的子类实例类型__abc_negative_cache__: 存储已确认不属于该ABC的类型(比如float)__abc_negative_cache_version__: 缓存版本标记
后续再创建自定义元类时,检测float是否属于numbers.Rational会直接读取负缓存结果,跳过子类遍历逻辑,从而避免触发错误的__subclasses__调用。
正确的实现方式
不要让元类直接继承numbers.Rational,如果要让自定义类成为numbers.Rational的子类,推荐两种方式:
方式1:使用abc.register注册
import numbers from abc import ABCMeta class Rational(metaclass=ABCMeta): # 实现numbers.Rational要求的抽象方法 @property def numerator(self): return 1 @property def denominator(self): return 1 # 将Rational注册为numbers.Rational的子类 numbers.Rational.register(Rational)
方式2:直接继承numbers.Rational并实现抽象方法
import numbers class Rational(numbers.Rational): # 必须实现numbers.Rational的抽象方法 @property def numerator(self): return 1 @property def denominator(self): return 1
两种方式都能正常通过isinstance检测,且不会触发TypeError:
print(isinstance(1, numbers.Rational)) # True print(isinstance(1., numbers.Rational)) # False print(isinstance(Rational(), numbers.Rational)) # True
内容的提问来源于stack exchange,提问作者user9413641
相关产品推荐
相关产品推荐

