You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

重定义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)的缓存机制和元类继承冲突:

  1. 元类继承的冲突
    numbers.Rational是基于abc.ABCMeta的抽象基类,而你定义的RationalMeta同时继承了type和numbers.Rational。这种继承方式会导致元类的方法解析顺序(MRO)混乱:当后续执行isinstance检测时,ABC的子类遍历逻辑会错误地将RationalMeta当作numbers.Rational的子类,进而调用未绑定的type.__subclasses__方法——该方法需要绑定到具体类实例才能执行,因此抛出TypeError。

  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 13:30:42