我试图为较长的字段创建一个具有较短别名的
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
出现两个问题:
.__setattr__
内部的 self 属性并没有什么坏处,但我相当确定这就是问题所在(重命名 __setattr__
可以解决问题,删除对 self.short_name_map
的引用也可以解决问题)。郑重声明:这是我想要的行为,但不必为每个字段编写两个修饰函数:
@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
你正在尝试做的事情是非常糟糕的做法。任何检查你的类但既没有找到属性也没有找到 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)
它并没有完全涵盖您描述的情况,但对于通过别名进行初始化、序列化/反序列化很有用。