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

如何在Python中标记无抽象方法的类为抽象类?兼容mypy且可复用

问题

需要实现一个无法直接实例化的类,但该类不存在未实现的抽象方法。参考网上方案可通过重写__new__方法实现:

class Example:
    def __new__(cls,*args,**kwargs):
        if cls is Example:
            raise TypeError("...")
        return super().__new__(cls,*args,**kwargs)

希望将这段逻辑封装为可复用的装饰器以避免重复代码,尝试的实现如下:

from typing import TypeVar, Type, Any, cast

C = TypeVar("C")
def abstract(cls:Type[C])->Type[C]:
    class Abstract(cls):
        def __new__(cls, *args:Any, **kwargs:Any)->"Abstract":
            if cls is Abstract:
                msg = "Abstract class {} cannot be instantiated".format(cls.__name__)
                raise TypeError(msg)
            return cast( Abstract, super().__new__(*args,**kwargs) )

    return Abstract

但运行mypy --strict时触发报错:error: Invalid base class "cls"。需要实现符合需求的可复用方案,同时通过mypy严格模式检查。

场景说明:在PySide6应用中继承QEvent类,基类仅定义属性(含默认实现的getter),因未注册到Qt事件系统,需禁止直接实例化该基类。

解决方案

mypy报错的核心原因是:装饰器内部动态创建子类时,直接使用cls作为基类违反了静态类型检查的规则。我们可以通过直接修改原类的__new__方法来规避这个问题,同时保留禁止实例化的逻辑。

最终实现代码

from typing import TypeVar, Type, Any, cast

C = TypeVar("C")
def abstract(cls: Type[C]) -> Type[C]:
    original_new = cls.__new__

    def __new__(subcls: Type[C], *args: Any, **kwargs: Any) -> C:
        if subcls is cls:
            raise TypeError(f"Abstract class {cls.__name__} cannot be instantiated")
        # 区分处理默认__new__和自定义__new__的调用逻辑
        if original_new is object.__new__:
            return cast(C, original_new(subcls))
        return cast(C, original_new(subcls, *args, **kwargs))

    # 类型忽略:mypy对修改类内置方法的类型检查较严格,但实际逻辑合法
    cls.__new__ = __new__  # type: ignore[assignment]
    return cls

使用示例(PySide6场景)

from PySide6.QtCore import QEvent

@abstract
class BaseCustomEvent(QEvent):
    def __init__(self, event_type: QEvent.Type):
        super().__init__(event_type)
    
    @property
    def data(self):
        return "default data"

# 直接实例化基类会抛出TypeError
# BaseCustomEvent(QEvent.Type.User)  # 触发错误:TypeError: Abstract class BaseCustomEvent cannot be instantiated

# 继承基类后可正常实例化
class ConcreteEvent(BaseCustomEvent):
    @property
    def data(self):
        return "concrete data"

event = ConcreteEvent(QEvent.Type.User)
print(event.data)  # 输出: concrete data

关键说明

  1. 避免动态子类:直接修改原类的__new__方法,绕过了mypy对动态基类的类型检查限制
  2. 保留原有逻辑:保存原类的__new__方法,确保继承链中自定义的构造逻辑不会被覆盖
  3. 兼容不同类:区分处理object.__new__和自定义__new__的调用方式,适配所有类的继承场景
  4. 类型忽略的合理性:# type: ignore[assignment]是因为mypy不允许直接修改类的__new__属性类型,但实际运行逻辑完全合法

内容的提问来源于stack exchange,提问作者Sam Coutteau

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 01:35:16