如何实现类似SQLAlchemy ORM的Mypy兼容类属性类型注解
实现类SQLAlchemy ORM的类型安全属性声明
要搞定类SQLAlchemy的模型声明效果——类里用Column(String(30))定义属性,实例化后self.name能被识别为字符串,同时Mypy无报错、IDE类型提示正常,核心是用描述符协议配合泛型类型注解。下面是具体解决方案:
1. 实现带泛型标记的Column描述符
用泛型明确标记属性的实际类型,让类型检查工具能正确关联声明与实例访问的类型:
from typing import Generic, TypeVar, Any, Optional T = TypeVar('T') class Column(Generic[T]): def __init__(self, type_: Any, max_length: Optional[int] = None): self.type = type_ self.max_length = max_length self._name: str = "" def __set_name__(self, owner: type, name: str): # 自动记录属性在类中的名称,不用手动指定 self._name = name def __get__(self, instance: Any, owner: type) -> T: # 实例访问时返回实际值,类访问时返回描述符自身 if instance is None: return self # 类型检查工具会处理类访问的情况 # 从实例的__dict__中取值,你也可以换成自己的存储逻辑 return instance.__dict__.get(self._name, None) def __set__(self, instance: Any, value: T) -> None: # 可选:添加运行时类型/规则校验 if self.max_length and isinstance(value, str) and len(value) > self.max_length: raise ValueError(f"值长度超过限制:{self.max_length}") instance.__dict__[self._name] = value
2. 定义模型基类(可选但推荐)
统一处理实例初始化逻辑,避免每个模型重复写初始化代码:
class Model: def __init__(self, **kwargs): for key, value in kwargs.items(): if hasattr(self, key): setattr(self, key, value)
3. 声明模型类并使用
现在可以像SQLAlchemy那样写模型,同时标注属性的实际类型,Mypy和IDE都会正确识别:
class User(Model): # 用Column[str]标记该属性实例化后是str类型 name: str = Column[str](str, max_length=30) age: int = Column[int](int) # 实例化测试 user = User(name="Alice", age=25) print(user.name) # IDE识别为str,Mypy无报错 user.age = 26 # 类型检查正常 user.name = 123 # Mypy会报错:Incompatible types in assignment (expression has type "int", variable has type "str")
4. Mypy配置优化
如果Mypy对描述符的类型识别还有问题,在项目根目录的mypy.ini中添加以下配置:
[mypy] strict = True enable_plugin = mypy.plugins.descriptors
关键细节
- 泛型的作用:
Column[T]是核心,它告诉类型检查工具,这个描述符在实例上访问时会返回T类型的值,解决了“声明时是Column类型,实例访问时是实际类型”的类型不兼容问题。 - __set_name__方法:自动绑定属性名,和SQLAlchemy的逻辑一致,不用手动维护属性名和存储键的映射。
- 运行时校验:在
__set__方法里可以加自定义校验,保证运行时的数据合法性,和类型检查形成互补。
内容的提问来源于stack exchange,提问作者salvaom
相关产品推荐
相关产品推荐

