![]() |
Flattening attribute access - Printable Version +- Python Forum (https://python-forum.io) +-- Forum: Python Coding (https://python-forum.io/forum-7.html) +--- Forum: General Coding Help (https://python-forum.io/forum-8.html) +--- Thread: Flattening attribute access (/thread-34096.html) |
Flattening attribute access - ruy - Jun-25-2021 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. RE: Flattening attribute access - deanhystad - Jun-25-2021 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}') RE: Flattening attribute access - ruy - Jun-25-2021 (Jun-25-2021, 01:18 PM)deanhystad Wrote: Instead of this: 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.
RE: Flattening attribute access - deanhystad - Jun-25-2021 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. RE: Flattening attribute access - ruy - Jun-25-2021 (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
RE: Flattening attribute access - ruy - Jun-25-2021 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: In the case f2.a I get: So I get different error messages in each case. I prefer the first case, with the 'PrintValue' type showing up in the message
|