如何在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
关键说明
- 避免动态子类:直接修改原类的
__new__方法,绕过了mypy对动态基类的类型检查限制 - 保留原有逻辑:保存原类的
__new__方法,确保继承链中自定义的构造逻辑不会被覆盖 - 兼容不同类:区分处理
object.__new__和自定义__new__的调用方式,适配所有类的继承场景 - 类型忽略的合理性:
# type: ignore[assignment]是因为mypy不允许直接修改类的__new__属性类型,但实际运行逻辑完全合法
内容的提问来源于stack exchange,提问作者Sam Coutteau
相关产品推荐
相关产品推荐

