Posts: 11
Threads: 3
Joined: Jun 2020
Hi there
I have a class PrintValue that contains as attribute an instances of another class PrintMetaData . In order to acess an attribute of the PrintMetaData object I would have to do something like object.meta_data.attribute . I wanted to flatten the access to the attribute to object.attribute . I wrote a class to do that and was wondering if that is considered bad practice. Obviously there is the possibility of name conflicts if the classes have attributes with the same name, but I could live with that. Here an example:
from dataclasses import dataclass
@dataclass
class PrintMetaData:
"""Print meta data for attr."""
long: str
abbr: str
unit: str = None
round_precision: int = 2
@dataclass
class PrintValue:
"""Printer wrapper for value."""
value: float
meta_data: PrintMetaData
def __getattr__(self, name):
try:
return getattr(self.meta_data, name)
except AttributeError:
return self.__getattribute__(name)
fwd_perp_md = PrintMetaData(
"Foward perpendicular",
r"F\textsubscript{P}",
"m"
)
fwd_perp_value = PrintValue(10, fwd_perp_md)
print(fwd_perp_value.meta_data)
print(fwd_perp_value.long)
print(fwd_perp_value.abbr)
print(fwd_perp_value.unit)
print(fwd_perp_value.round_precision) This is small and seems to work, but I was wondering if that is still ok with classes with more internal objects.
Posts: 6,772
Threads: 20
Joined: Feb 2020
Jun-25-2021, 01:18 PM
(This post was last modified: Jun-25-2021, 01:22 PM by deanhystad.)
Instead of this:
try:
return getattr(self.meta_data, name)
except AttributeError:
return self.__getattribute__(name) You should simply do this:
return getattr(self.meta_data, name) __getattr__ is only called if "name" cannot be resolved by normal means. So if "name" is an attribute of PrintValue, __getattr__ is not called. Worse, by catching the error and calling self.__getattribute__(name), any "name" that is not an attribute of the PrintValue object or the PrintMetaData object results in a recursion of caught exceptions.
I don't know if using __getattr__ this way is good or bad, but I do something similar in some code I am working on, and while researching have found other places where this is done. It is not an unusual use of __getattr__.
If you are going to reference multiple objects this way be aware that you will need to handle exceptions. For example, if your class has internal objects "object_a" and "object_b", this complicates things.
def __getattr__(self, "name"):
while part in (self.object_a, self.object_b):
try:
return getattr(part, name):
except NameError:
pass:
raise NameError(f'{self.__class__.__name__} does not understand {name}')
Posts: 11
Threads: 3
Joined: Jun 2020
(Jun-25-2021, 01:18 PM)deanhystad Wrote: Instead of this:
try:
return getattr(self.meta_data, name)
except AttributeError:
return self.__getattribute__(name) You should simply do this:
return getattr(self.meta_data, name) __getattr__ is only called if "name" cannot be resolved by normal means. So if "name" is an attribute of PrintValue, __getattr__ is not called. Worse, by catching the error and calling self.__getattribute__(name), any "name" that is not an attribute of the PrintValue object or the PrintMetaData object results in a recursion of caught exceptions.
I don't know if using __getattr__ this way is good or bad, but I do something similar in some code I am working on, and while researching have found other places where this is done. It is not an unusual use of __getattr__.
If you are going to reference multiple objects this way be aware that you will need to handle exceptions. For example, if your class has internal objects "object_a" and "object_b", this complicates things.
def __getattr__(self, "name"):
while part in (self.object_a, self.object_b):
try:
return getattr(part, name):
except NameError:
pass:
raise NameError(f'{self.__class__.__name__} does not understand {name}')
Yours was my first solution. The only downside it the error message Refers to the embedded object, not the container you from where actually tried to access the attribute. Kind of bothered me. I tried raise the AttributeError myself and manually setting the error message, but didn't like that.
Posts: 6,772
Threads: 20
Joined: Feb 2020
AttributeError would be the wrong type of error for this. Your code is looking for an attribute that it fails to find. That raises a NameError.
Posts: 11
Threads: 3
Joined: Jun 2020
Jun-25-2021, 08:18 PM
(This post was last modified: Jun-25-2021, 08:27 PM by ruy.)
(Jun-25-2021, 07:19 PM)deanhystad Wrote: AttributeError would be the wrong type of error for this. Your code is looking for an attribute that it fails to find. That raises a NameError.
I'm runing python 3.9.5 and get the AttributeError , such as in this simple example:
class Test:
pass
t = Test()
t.a Error: Traceback (most recent call last):
File "C:\Users\ruy\Google Drive\PYTHON\cj8-qualifier\qualifier\untitled10.py", line 12, in <module>
t.a
AttributeError: 'Test' object has no attribute 'a'
Posts: 11
Threads: 3
Joined: Jun 2020
Jun-25-2021, 08:26 PM
(This post was last modified: Jun-25-2021, 08:28 PM by ruy.)
In my original case i get these 2 errors denpending on implimentation:
from dataclasses import dataclass
@dataclass
class PrintMetaData:
"""Print meta data for attr."""
long: str
abbr: str
unit: str = None
round_precision: int = 2
@property
def s(self):
return self.long + self.abbr
@dataclass
class PrintValue:
"""Printer wrapper for attr."""
value: float
meta_data: PrintMetaData
def __getattr__(self, name):
try:
return getattr(self.meta_data, name)
except AttributeError:
return self.__getattribute__(name)
@property
def t(self):
return self.round_precision*100
@dataclass
class PrintValue2:
"""Printer wrapper for attr."""
value: float
meta_data: PrintMetaData
def __getattr__(self, name):
return getattr(self.meta_data, name)
@property
def t(self):
return self.round_precision*100
fwd_perp_md = PrintMetaData(
"Foward perpendicular",
r"F\textsubscript{P}",
"m"
)
f = PrintValue(10, fwd_perp_md)
f2 = PrintValue2(10, fwd_perp_md) In the case f.a I get:
Error: Traceback (most recent call last):
File "<ipython-input-2-47da146b5ce6>", line 1, in <module>
f.a
File "C:\Users\ruy\Google Drive\PYTHON\attribute.py", line 35, in __getattr__
return self.__getattribute__(name)
AttributeError: 'PrintValue' object has no attribute 'a'
In the case f2.a I get:
Error: Traceback (most recent call last):
File "<ipython-input-3-cedb95a18547>", line 1, in <module>
f2.a
File "C:\Users\ruy\Google Drive\PYTHON\attribute.py", line 50, in __getattr__
return getattr(self.meta_data, name)
AttributeError: 'PrintMetaData' object has no attribute 'a'
So I get different error messages in each case. I prefer the first case, with the 'PrintValue' type showing up in the message
|