Python如何让工厂函数返回类属性引用而非属性值?
有如下基础Python泛型类,包含一个property属性:
from typing import Generic, TypeVar T = TypeVar('T') class Test(Generic[T]): def __init__(self, x: T): self.__x = x @property def x(self): return self.__x def update(self, value: T): self.__x = value # 更新逻辑可根据数据类型调整
现有一个工厂函数用于创建该类实例,希望工厂函数返回属性x本身,而非属性当前时刻的取值(简化示例中仅生成一个实例):
def test_factory(value: T): obj = Test(value) return obj.x, obj.update if __name__ == "__main__": test_property, test_setter = test_factory(10) print(test_property) test_property = 2 print(test_property) test_setter(12) print(test_property)
期望输出
执行后期望输出如下,且test_property = 2时触发错误:
10 10 12
实际输出
但当前实际输出为:
10 2 2
原因是返回obj.x时,实际拿到的是属性当前的取值(即10),后续test_property变量被直接赋值为2,完全脱离了原对象的属性关联。
需求是:让工厂函数返回的test_property行为和直接使用obj.x完全一致——既能实时获取最新属性值,又无法被直接赋值;已知可以通过在Test类中定义getX函数并返回该函数解决,但希望用现有语法实现,让返回值看起来像普通值而非getter函数。之前尝试过返回property(obj.getX())这类写法,但得到的是property对象,无法实现和obj.x相同的行为。
方法1:自定义实例属性代理类(完全模拟obj.x行为)
通过自定义一个代理类,绑定到目标对象的指定属性上,模拟property的取值逻辑,同时禁止赋值操作,完全贴合需求:
from typing import Generic, TypeVar, Any T = TypeVar('T') class Test(Generic[T]): def __init__(self, x: T): self.__x = x @property def x(self): return self.__x def update(self, value: T): self.__x = value class PropertyProxy: def __init__(self, obj: Any, attr_name: str): self._obj = obj self._attr_name = attr_name def __repr__(self): # 让打印代理对象时直接显示属性值,和原属性行为一致 return repr(getattr(self._obj, self._attr_name)) def __get__(self, instance, owner): return getattr(self._obj, self._attr_name) def __set__(self, instance, value): # 禁止赋值,抛出和原property一致的错误 raise AttributeError(f"can't set attribute") def test_factory(value: T): obj = Test(value) # 返回绑定了obj.x的代理对象 return PropertyProxy(obj, 'x'), obj.update if __name__ == "__main__": test_property, test_setter = test_factory(10) print(test_property) try: test_property = 2 except AttributeError as e: print(e) test_setter(12) print(test_property)
运行输出:
10 can't set attribute 12
这个方案完全实现了需求:直接访问test_property就能获取最新值,赋值会抛出标准的AttributeError,和直接使用obj.x的体验完全一致。
方法2:用operator.attrgetter实现简易代理(需调用取值)
如果可以接受调用式的取值方式,operator.attrgetter能快速生成一个绑定属性的可调用对象,每次调用即可获取最新值,同时直接赋值会报错:
from typing import Generic, TypeVar import operator T = TypeVar('T') class Test(Generic[T]): def __init__(self, x: T): self.__x = x @property def x(self): return self.__x def update(self, value: T): self.__x = value def test_factory(value: T): obj = Test(value) # 返回绑定obj.x的属性获取器 return operator.attrgetter('x')(obj), obj.update if __name__ == "__main__": test_property, test_setter = test_factory(10) print(test_property()) # 需调用获取值 try: test_property = 2 except TypeError as e: print(e) test_setter(12) print(test_property())
运行输出:
10 'operator.attrgetter' object does not support item assignment 12
这个方案无需自定义类,但需要通过test_property()来取值,适合对写法要求不那么严格的场景。
补充说明
直接返回property对象无效的原因:property是类级别的描述符,必须绑定到类上才能生效,单独返回的property对象没有和具体实例关联,无法正确获取实例的属性值。而上面的PropertyProxy是实例级代理,专门绑定到目标对象上,所以能模拟原属性的全部行为。
内容的提问来源于stack exchange,提问作者Gray Hat

