Python:尝试访问 self.__setattr__ 中的实例属性

问题描述 投票:0回答:1

我试图为较长的字段创建一个具有较短别名的

dataclass
。由于我希望能够添加新字段(具有长名称和短名称),而不必编写
@property
装饰的“getter”和
@short_name.setter
装饰的“setter”,因此我决定使用将长名称映射到短名称的字典名称,并实现自定义
__getattr__
__setattr__
方法。但是,我的内核崩溃了(在 JupyterLab 中运行代码时)/我收到了递归错误(运行脚本时)。经过一番尝试和错误后,我似乎无法通过
self
方法内的
__setattr__
访问任何实例或类属性。我当前(失败)的实现是

@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):
        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):
        short_name = self.short_name_map[name]
        self.__dict__[short_name ] = value

出现两个问题:

  1. 问题从何而来?我认为访问
    .__setattr__
    内部的 self 属性并没有什么坏处,但我相当确定这就是问题所在(重命名
    __setattr__
    可以解决问题,删除对
    self.short_name_map
    的引用也可以解决问题)。
  2. 我该如何解决这个问题?还是有一些我没有考虑到的潜在范式?

郑重声明:这是我想要的行为,但不必为每个字段编写两个修饰函数:

@dataclass
class Foo:
    very_long_name_of_a_number: int

    @property
    def number(self) -> int:
        return self.very_long_name_of_a_number

    @number.setter
    def short_name(self, value: int):
        self.very_long_name_of_a_number= value
python properties python-dataclasses setattr
1个回答
0
投票

你正在尝试做的事情是非常糟糕的做法。任何检查你的类但既没有找到属性也没有找到 getter/setter 的人将会真正感到困惑。

显式优于隐式。

唯一好的方法是您在问题中提到的方法:

@dataclass
class Foo:
    very_long_name_of_a_number: int

    @property
    def number(self) -> int:
        return self.very_long_name_of_a_number

    @number.setter
    def number(self, value: int):
        self.very_long_name_of_a_number = value

我强烈建议您将其用于您的目的,无论您有多少属性以及它们的名称有多长。

您可能还想考虑使用

Pydantic
而不是
dataclass
,因为它提供了可能对您有用的
alias
属性:

from pydantic import BaseModel, Field


class Foo(BaseModel):
    very_long_name_of_a_number: int = Field(alias="number")


foo = Foo(number=2)

它并没有完全涵盖您描述的情况,但对于通过别名进行初始化、序列化/反序列化很有用。

© www.soinside.com 2019 - 2024. All rights reserved.