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

Python dataclass中__setattr__访问属性引发递归错误的排查与解决

Dataclass字段短别名实现的递归错误解析

1. 问题根源

  • 无限递归触发:重写__setattr__后,所有实例属性的赋值操作(包括dataclass初始化时设置short_name_map)都会进入自定义的__setattr__方法。而你的__setattr__第一行就尝试访问self.short_name_map,此时该属性还未被存入实例的__dict__,Python会触发属性查找逻辑,最终调用__getattr__;同时__getattr__中错误地写了self._short_name_map(多了下划线),会再次触发__getattr__,形成无限递归,导致栈溢出(递归错误)或内核崩溃。
  • 属性名匹配错误:__getattr__里引用的_short_name_map和实际定义的short_name_map不匹配,即使没有递归,也无法正确获取映射字典。

2. 解决方案

要避免递归,核心是在自定义属性方法中绕开自定义的访问逻辑,直接操作底层存储或使用类属性,同时修正属性名错误。下面提供几种可行方案:

方案一:修正__getattr__和__setattr__,直接操作__dict__

from dataclasses import dataclass, field

@dataclass
class Foo:
    very_long_name_of_a_number: int
    very_long_name_of_a_string: str

    short_name_map: dict = field(
        default_factory=lambda: {
            "number": "very_long_name_of_a_number",
            "string": "very_long_name_of_a_string",
        },
        init=False,
        repr=False,
    )

    def __getattr__(self, name):
        # 直接从__dict__取映射,避免触发递归
        if name in self.__dict__.get('short_name_map', {}):
            return getattr(self, self.short_name_map[name])
        raise AttributeError(
            f"'{self.__class__.__name__}' object has no attribute '{name}'"
        )

    def __setattr__(self, name, value):
        # 先从__dict__获取映射,绕开自定义属性逻辑
        short_map = self.__dict__.get('short_name_map')
        if short_map and name in short_map:
            target_attr = short_map[name]
            self.__dict__[target_attr] = value
        else:
            # 非别名属性调用父类方法正常设置
            super().__setattr__(name, value)

方案二:将映射设为类属性

映射规则是类级别的固定配置,无需每个实例存储一份,改为类属性可彻底避免实例属性访问的递归问题:

from dataclasses import dataclass

@dataclass
class Foo:
    very_long_name_of_a_number: int
    very_long_name_of_a_string: str

    # 类属性,所有实例共享
    short_name_map = {
        "number": "very_long_name_of_a_number",
        "string": "very_long_name_of_a_string",
    }

    def __getattr__(self, name):
        if name in self.short_name_map:
            return getattr(self, self.short_name_map[name])
        raise AttributeError(
            f"'{self.__class__.__name__}' object has no attribute '{name}'"
        )

    def __setattr__(self, name, value):
        if name in self.short_name_map:
            target_attr = self.short_name_map[name]
            self.__dict__[target_attr] = value
        else:
            super().__setattr__(name, value)

方案三:元类自动生成别名属性

如果需要更灵活的配置(比如通过字段注解指定别名),可以用元类在类创建时自动生成@property装饰的别名,无需手动编写每个属性的getter和setter:

from dataclasses import dataclass, fields

class AliasMeta(type):
    def __new__(cls, name, bases, attrs):
        new_cls = super().__new__(cls, name, bases, attrs)
        # 遍历dataclass字段,读取metadata中的别名配置
        for field in fields(new_cls):
            alias = field.metadata.get('alias')
            if alias:
                # 动态生成getter和setter
                def getter(self, field_name=field.name):
                    return getattr(self, field_name)
                def setter(self, value, field_name=field.name):
                    setattr(self, field_name, value)
                setattr(new_cls, alias, property(getter, setter))
        return new_cls

@dataclass
class Foo(metaclass=AliasMeta):
    very_long_name_of_a_number: int = field(metadata={'alias': 'number'})
    very_long_name_of_a_string: str = field(metadata={'alias': 'string'})

测试示例:

foo = Foo(123, "test")
print(foo.number)  # 输出: 123
foo.string = "new content"
print(foo.very_long_name_of_a_string)  # 输出: new content

内容的提问来源于stack exchange,提问作者Lukas

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 12:35:04