Today, my colleagues reported that a basic library I wrote has a bug, It's probably that the class you wrote clearly has attributes foo
, But it will throw an exception like the one below ,
AttributeError: 'A' object has no attribute 'foo'
This is very confusing , Because the function that throws the exception is of the base class __getattr__()
Method , So he asked me to solve it .
I think the code is also confused , This foo
Right there , This bug It gave me an illusion that seeing is not believing , I can't find the direction for a moment . Suddenly I found this foo
There is one on top @property
The hat of ( Decorator ), Why , Is it related to this ? So search for , I found this article Correct handling of AttributeError in __getattr__ when using property, The high praise answer above perfectly answers this question . Let me take this question and answer code as an example , In this way, you can only read my article .
First of all, there is such code , This code is purely for example , There is no logical meaning ,
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)
apparently , You will think that the exception thrown is AttributeError: 'A' object has no attribute 'moo'
, But the exception actually thrown is AttributeError: 'A' object has no attribute 'G'
And the stack that spits out is like this :
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'
As __getattr__()
The author of , It's really annoying .
Abate one's breath , So why __getattr__()
Will the front AtrributeError
Abnormally eaten ? This is to review __getatrr__()
When to call , The document says :
Called when the default attribute access fails with an AttributeError (either __getattribute__() raises an AttributeError because name is not an instance attribute or an attribute in the class tree for self; or __get__() of a name property raises AttributeError).
Simply speaking , When looking for attributes ,Python
Will call first __getattribute__()
, If you can't find it ( Throw out AtrributeError
abnormal ), Will try to call __getattr__()
. Another situation is if property
Of __get__()
Method throw AttributeError
When abnormal , Will also try to call __getattr__()
.
So back to the above example , Use @property
The decorated member function becomes a descriptor descriptor
, When accessing it , What is actually called is the descriptor __get__()
Method , obviously ,print(a.G)
First called. a.G.__get__
, Then it calls a.F.__get__
, This method refers to self.moo
, because moo
Property does not exist , Just throw it out AtrributeError
. According to the description of the above document , This will call a
Of __getattr__()
Method , According to the code above , This __getattr__()
It will also be thrown out AtrributeError
, therefore Python Just eat the previous exception , It only shows the abnormality that the cow's head is not right behind the horse's mouth .
In this case , That is to be in __get__()
Throw out AtrributeError
When catching abnormal , Rather than by Python To deal with . To achieve this , In this example and the code of our project , Are overloaded __getatrribute__()
instead of __getattr__()
. Modify the above code as follows :
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)
When it's executed again , You will find that the thrown exception meets your expectations :
Traceback (most recent call last):
...
AttributeError: 'A' object has no attribute 'moo'
And after our project made the same changes , It also solved the problem . This accident tells us , Don't overload easily __getattr__()
.