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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 20:19:54