I have a difficulty implementing properties and __getattr__
so that
when an error happens, it is reported correctly. This is my MWE (python 3.6):
class A:
@property
def F(self):
return self.moo # here should be an error
@property
def G(self):
return self.F
def __getattr__(self, name):
print('call of __getattr__ with name =', name)
if name == 'foo':
return 0
raise AttributeError("'{}' object has no attribute '{}'".format(type(self).__name__, name))
a = A()
print(a.G)
The output is as follows:
call of __getattr__ with name = moo
call of __getattr__ with name = F
call of __getattr__ with name = G
Traceback (most recent call last):
line 18 in <module>
print(a.G)
line 15, in __getattr__
raise AttributeError("'{}' object has no attribute '{}'".format(type(self).__name__, name))
AttributeError: 'A' object has no attribute 'G'
But the error that should be raised is:
AttributeError: 'A' object has no attribute 'moo'
I know that properties and attributes in the __dict__
are attempted before __getattr__
is called in an error-free scenario.
It seems incorrect to me that when a property exists but fails, __getattr__
is still attempted instead of letting the error from the property to go through. How can this be avoided?
The initial error message that was generated about failing to get attribute 'foo'
has been lost. The final error message 'A' object has no attribute 'G'
is particularly misleading and annoying. How to implement __getattr__
in order to see the initial error?
(EDIT) A related problem is simultaneously to achieve that
hasattr(a, 'moo')
returns False
while hasattr(a, 'G')
returns True
or raises an exception of the missing 'moo'
attribute. Does that make sense?
Python getattr() function is used to get the value of an object's attribute and if no attribute of that object is found, default value is returned.
Python | getattr() method Python getattr() function is used to access the attribute value of an object and also gives an option of executing the default value in case of unavailability of the key.
To avoid the AttributeError in Python code, a check should be performed before referencing an attribute on an object to ensure that it exists. The Python help() function can be used to find out all attributes and methods related to the object. To resolve the AttributeError , a try-except block can be used.
The getattr approach is slower than the if approach, reducing an insignificant ~3% the performance if bar is set and a considerable~35% if is not set.
First, a little heads up as to why this happens. From the doc on __getattr__
:
Called when the default attribute access fails with an AttributeError [...] or
__get__()
of a name property raises AttributeError.
In this case, since you are using @property
, we are looking at an AttributeError
raised from the __get__
method of the property F
when trying to recover self.moo
. This is what your call stack looks like at that moment.
__main__
a.G.__get__
a.F.__get__
a.__getattr__ # called with 'moo' <-- this is where the error is raised
The attribute getter protocol sees an error being raised from inside a.F.__get__
, it thus fallback on calling a.__getattr__('F')
and that despite the fact the error had been raised because of 'moo'
. The same then happens for a.G.__get__
This behaviour is considered normal in Python, since the top-most property that failed to return a value is indeed a.G
.
Now what you want is for an AttributeError
raised by a __get__
method to bubble up instead of being caught. To do that you need not to have a __getattr__
method.
Thus, in this particular case, what you want to use is __getattribute__
instead.
Of course, with this solution you have to make sure yourself not to override an existing attribute.
class A:
@property
def F(self):
return self.moo # here should be an error
@property
def G(self):
return self.F
def __getattribute__(self, name):
print('call of __getattribute__ with name =', name)
if name == 'foo':
return 0
else:
return super().__getattribute__(name)
A().G
call of __getattribute__ with name = G
call of __getattribute__ with name = F
call of __getattribute__ with name = moo
Traceback (most recent call last):
...
AttributeError: 'A' object has no attribute 'moo'
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With