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

如何动态生成符合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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 09:45:27