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

定义抽象类属性时NotImplementedError意外触发的原因排查

抽象类属性实现中NotImplementedError意外触发问题

之前基于自定义的classProp和ClassPropMeta实现了类属性机制,尝试结合@abstractmethod定义子类必须实现的抽象类属性时,运行代码意外触发了NotImplementedError。调试发现cls参数指向类B,调用栈显示触发点关联abc模块的_abc_init流程,但无法定位B.X的具体调用时机。

最小复现代码

from abc import ABCMeta, abstractmethod
from typing import Any


class classProp[T, P](property):
    def __get__(self, instance: T, owner: type[T] = None) -> P:
        return self.fget(owner)


class ClassPropMeta(ABCMeta):
    def __setattr__(cls, name: str, value: Any):
        if isinstance(desc := vars(cls).get(name), classProp) and not callable(
            desc.fset
        ):
            raise AttributeError("can't set attribute")
        return super().__setattr__(name, value)


class A(metaclass=ClassPropMeta):
    __slots__ = ()

    @classProp
    @abstractmethod
    def X(cls):
        # breakpoint()
        raise NotImplementedError


class B(A):
    ...
    # @classProp
    # def X(cls):
    #     return 1


class C(B):
    @classProp
    def X(cls):
        return 1


print(C.X)
# 运行结果:触发NotImplementedError

调用栈信息

> d:\path\to\my\example.py(31)X()
-> breakpoint()
(Pdb) w
  d:\path\to\my\example.py(35)<module>()
-> class B(A):
  <frozen abc>(107)__new__()
  d:\path\to\my\example.py(13)__get__()
-> return self.fget(owner)
> d:\path\to\my\example.py(31)X()
-> breakpoint()

问题原因

ABCMeta在创建子类(如B)时,会执行抽象成员的初始化校验流程,在此过程中会触发classProp的__get__方法。由于classProp的__get__直接调用了父类A中标记为抽象的X方法,而该方法内部硬编码了NotImplementedError,导致子类创建阶段就抛出错误,而非由abc机制正常处理抽象成员的未实现校验。

同时,classProp未被abc识别为抽象成员载体,导致abc无法正确判断子类是否已实现该属性,反而触发了属性访问逻辑。

解决方案

方案1:调整抽象类属性的错误抛出逻辑

移除抽象方法内的NotImplementedError,让abc的原生机制处理抽象成员校验;同时让classProp携带抽象标记,被abc识别:

from abc import ABCMeta, abstractmethod
from typing import Any


class classProp[T, P](property):
    def __get__(self, instance: T, owner: type[T] = None) -> P:
        return self.fget(owner)
    
    def __init__(self, fget=None, fset=None, fdel=None, doc=None):
        super().__init__(fget, fset, fdel, doc)
        # 传递抽象方法标记,让abc识别该描述符为抽象成员
        if fget and hasattr(fget, '__isabstractmethod__'):
            self.__isabstractmethod__ = fget.__isabstractmethod__


class ClassPropMeta(ABCMeta):
    def __setattr__(cls, name: str, value: Any):
        if isinstance(desc := vars(cls).get(name), classProp) and not callable(
            desc.fset
        ):
            raise AttributeError("can't set attribute")
        return super().__setattr__(name, value)


class A(metaclass=ClassPropMeta):
    __slots__ = ()

    @classProp
    @abstractmethod
    def X(cls):
        # 移除手动抛出的错误,由abc机制处理
        pass


class B(A):
    ...
    # 未实现X,B会被标记为抽象类,无法实例化,但类本身可以正常定义
    # @classProp
    # def X(cls):
    #     return 1


class C(B):
    @classProp
    def X(cls):
        return 1


print(C.X)  # 输出:1
# 若尝试实例化B:b = B(),会抛出TypeError: Can't instantiate abstract class B with abstract method X

方案2:修改classProp的__get__逻辑,避免子类初始化阶段触发父类方法

通过检查子类是否已实现该属性,避免在abc校验流程中调用父类的抽象方法:

from abc import ABCMeta, abstractmethod
from typing import Any


class classProp[T, P](property):
    def __get__(self, instance: T, owner: type[T] = None) -> P:
        if owner is None:
            owner = type(instance)
        # 检查子类是否已实现该属性
        if self.name in owner.__dict__:
            # 子类已实现,调用子类的方法
            return self.fget(owner)
        # 子类未实现,判断是否为抽象类校验阶段
        if getattr(owner, '__abstractmethods__', None) and self.name in owner.__abstractmethods__:
            # 属于抽象校验,抛出AttributeError让abc处理
            raise AttributeError(f"Abstract class property {self.name} not implemented")
        # 其他情况调用父类方法(可选,若需要保留原错误逻辑)
        return self.fget(owner)
    
    def __set_name__(self, owner, name):
        # 记录属性名称,用于后续检查
        self.name = name


class ClassPropMeta(ABCMeta):
    def __setattr__(cls, name: str, value: Any):
        if isinstance(desc := vars(cls).get(name), classProp) and not callable(
            desc.fset
        ):
            raise AttributeError("can't set attribute")
        return super().__setattr__(name, value)


class A(metaclass=ClassPropMeta):
    __slots__ = ()

    @classProp
    @abstractmethod
    def X(cls):
        raise NotImplementedError


class B(A):
    ...


class C(B):
    @classProp
    def X(cls):
        return 1


print(C.X)  # 输出:1

内容的提问来源于stack exchange,提问作者F. X. P.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 08:29:52