I was trying to create a dataclass
with shorter aliases for longer fields. Since I wanted to be able to add new fields (with long and short names) without having to write a @property
decorated "getter" and a @short_name.setter
decorated "setter", I decided to use a dictionary mapping long names to short names, and implement custom __getattr__
and __setattr__
methods. However, my kernel crashed (when running the code in JupyterLab) / I got a Recursion error (when running the script). After some trial and error, it seems I cannot access any instance or class attribute via self
inside the __setattr__
method. My current (failing) implementation is
@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
Two questions arise:
.__setattr__
, but I am fairly sure that is the problem (renaming __setattr__
solves the problem, as does deleting the reference to self.short_name_map
).For the record: this is the behaviour I wanted to have, but without having to write the two decorated functions for every field:
@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
What you’re trying to do is extremely bad practice. Anyone who inspect your class and finds neither a property, nor a getter/setter will be truly confused.
Explicit is better than implicit.
The only good approach is the one you mentioned in the question:
@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
I highly recommend using it for your purposes, no matter how many attributes you have and how long their names are.
You may also want to consider using Pydantic
instead of dataclass
, as it provides an alias
attribute that may be useful to you:
from pydantic import BaseModel, Field
class Foo(BaseModel):
very_long_name_of_a_number: int = Field(alias="number")
foo = Foo(number=2)
It doesn't quite cover cases you described but can be useful for initialization, serializing / deserializing by alias.