如何动态生成符合mypy规范的属性Setter?基类属性批量优化
解决方案:用泛型描述符实现简洁的可注入计算属性
针对你遇到的问题,自定义泛型描述符是最优方案——既可以批量复用属性的get/set逻辑,又能让mypy正确识别类型,完全避免__setattr__带来的类型检查问题。
核心思路
用描述符封装「优先使用注入值,无注入值则调用子类实现的计算方法」的逻辑,每个属性只需一行声明,子类仅需实现对应的计算方法即可。
1. 实现泛型描述符类
这个描述符会自动处理属性的读取和赋值逻辑,同时通过泛型保证mypy能识别属性类型:
from typing import Generic, TypeVar, Any, Optional T = TypeVar("T") class CachedComputedProperty(Generic[T]): def __init__(self, attr_name: str): self.attr_name = attr_name # 用私有属性存储注入的值 self._storage_key = f"_{attr_name}_injected" def __get__(self, instance: Any, owner: type) -> T: if instance is None: return self # 优先返回注入的值 if hasattr(instance, self._storage_key): return getattr(instance, self._storage_key) # 无注入值时调用子类的计算方法 getter_method = f"_get_{self.attr_name}" if not hasattr(instance, getter_method): raise NotImplementedError(f"子类必须实现 {getter_method} 方法") return getattr(instance, getter_method)() def __set__(self, instance: Any, value: T) -> None: # 存储注入的值 setattr(instance, self._storage_key, value)
2. 在基类中声明属性
基类只需用描述符声明属性,无需重复写property、setter和计算方法的模板代码:
class Base: # 直接用描述符声明属性,mypy会识别类型 value: int = CachedComputedProperty("value") name: str = CachedComputedProperty("name") # 子类需要实现的计算方法模板 def _get_value(self) -> int: raise NotImplementedError def _get_name(self) -> str: raise NotImplementedError
3. 子类实现计算逻辑
子类仅需实现对应的_get_xxx方法,注入值的逻辑完全由描述符处理:
class Child(Base): def _get_value(self) -> int: # 模拟开销极大的计算逻辑 print("执行高开销计算:value") return 42 def _get_name(self) -> str: print("执行高开销计算:name") return "默认名称"
4. 使用示例
obj = Child() # 第一次读取:调用子类的计算方法 print(obj.value) # 输出:执行高开销计算:value → 42 print(obj.name) # 输出:执行高开销计算:name → 默认名称 # 注入值后,读取会直接用注入的值 obj.value = 100 obj.name = "自定义名称" print(obj.value) # 输出:100 print(obj.name) # 输出:自定义名称
批量声明属性(可选)
如果属性数量较多,可以用类装饰器进一步简化代码,批量生成描述符属性:
def add_computed_properties(props: dict[str, type]) -> type: def decorator(cls: type) -> type: for prop_name, prop_type in props.items(): setattr(cls, prop_name, CachedComputedProperty(prop_name)) return cls return decorator # 用装饰器批量声明属性 @add_computed_properties({"value": int, "name": str, "score": float}) class Base: def _get_value(self) -> int: raise NotImplementedError def _get_name(self) -> str: raise NotImplementedError def _get_score(self) -> float: raise NotImplementedError
关键优势
- 类型安全:mypy能正确识别每个属性的类型,赋值时会做类型检查,不会出现
__setattr__允许任意属性赋值的问题。 - 代码简洁:每个属性只需一行声明,避免了重复编写property、setter和模板方法的冗余代码。
- 扩展性强:可以在描述符中轻松添加额外逻辑,比如缓存计算结果(避免重复执行高开销计算):
def __get__(self, instance: Any, owner: type) -> T: if instance is None: return self if hasattr(instance, self._storage_key): return getattr(instance, self._storage_key) getter_method = f"_get_{self.attr_name}" if not hasattr(instance, getter_method): raise NotImplementedError(f"子类必须实现 {getter_method} 方法") # 计算后缓存结果,避免重复计算 result = getattr(instance, getter_method)() setattr(instance, self._storage_key, result) return result
内容的提问来源于stack exchange,提问作者Some Guy
相关产品推荐
相关产品推荐

