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

如何在Python数据类中结合default_factory使用描述符?

解决描述符与dataclass default_factory配合的问题

问题根源

你遇到的问题核心在于Python描述符协议仅对类级别的属性生效:

  • 直接在dataclass中赋值attribute1: Verbose_attribute = Verbose_attribute()时,描述符是Foo类的属性,访问实例属性时会触发描述符的__get__方法。
  • 用field(default_factory=...)创建描述符实例时,该实例会被存入Foo实例的__dict__中,Python访问实例属性时会直接返回这个对象,不会触发描述符协议。

解决方案:代理描述符

我们可以实现一个代理描述符,将类级别的描述符逻辑与实例级别的独立状态分离,既满足每个实例有独立的描述符实例(适配你的MutableSequence场景),又能触发描述符协议:

from dataclasses import dataclass, field
import numpy as np

class DescriptorProxy:
    def __init__(self, factory):
        self.factory = factory
        self._instance_attr_name = None

    def __set_name__(self, owner, name):
        # 为实例存储实际描述符的属性名添加前缀,避免冲突
        self._instance_attr_name = f"_{name}"

    def __get__(self, obj, type=None):
        if obj is None:
            return self
        # 若实例还没有创建对应的描述符,调用factory生成
        if self._instance_attr_name not in obj.__dict__:
            obj.__dict__[self._instance_attr_name] = self.factory()
        # 调用实际描述符的__get__方法
        actual_descriptor = obj.__dict__[self._instance_attr_name]
        return actual_descriptor.__get__(obj, type)

    def __set__(self, obj, value):
        # 确保实例已初始化实际描述符
        if self._instance_attr_name not in obj.__dict__:
            obj.__dict__[self._instance_attr_name] = self.factory()
        actual_descriptor = obj.__dict__[self._instance_attr_name]
        actual_descriptor.__set__(obj, value)

# 修正你的描述符:每个实例拥有独立状态
class Verbose_attribute:
    def __init__(self):
        self.a = np.arange(2)  # 每个描述符实例有自己的数组

    def __get__(self, obj, type=None) -> object:
        print("accessing the attribute to get the value")
        return self.a[-1]

    def __set__(self, obj, value) -> None:
        print("accessing the attribute to set the value")
        self.a[-1] = value

@dataclass
class Foo():
    # 用代理描述符包装你的描述符工厂
    attribute1: Verbose_attribute = DescriptorProxy(lambda: Verbose_attribute())

my_foo_object = Foo()
print(my_foo_object.attribute1)  # 输出: accessing the attribute to get the value \n 1
my_foo_object.attribute1 = 3
print(my_foo_object.attribute1)  # 输出: accessing the attribute to set the value \n accessing the attribute to get the value \n 3

适配MutableSequence场景

对于继承自MutableSequence的描述符,只需将Verbose_attribute替换为你的自定义序列类即可,代理描述符的逻辑完全通用:

from collections.abc import MutableSequence

class CustomMutableSequence(MutableSequence):
    # 实现MutableSequence的抽象方法:__getitem__, __setitem__, __delitem__, __len__, insert
    def __init__(self):
        self._data = []
    
    def __getitem__(self, idx):
        print(f"getting index {idx}")
        return self._data[idx]
    
    def __setitem__(self, idx, value):
        print(f"setting index {idx} to {value}")
        self._data[idx] = value
    
    def __delitem__(self, idx):
        del self._data[idx]
    
    def __len__(self):
        return len(self._data)
    
    def insert(self, idx, value):
        self._data.insert(idx, value)

# 在dataclass中使用
@dataclass
class Foo():
    sequence_attr: CustomMutableSequence = DescriptorProxy(lambda: CustomMutableSequence())

关键说明

  • DescriptorProxy作为类级别的描述符,负责每个实例的实际描述符初始化和方法代理。
  • 实际描述符(如Verbose_attribute或自定义序列类)可以持有独立状态,每个Foo实例会拥有专属的描述符实例。

内容的提问来源于stack exchange,提问作者Lech Wiktor Piotrowski

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 01:07:55