I'm re-implementing __getattribute__ for a class.
I want to notice any incorrect (meaning failures are expected, of course) failures of providing attributes (because the __getattribute__ implementation turned out quite complex). For that I log a warning if my code was unable to find/provide the attribute just before raising an AttributeError.
I'm aware:
__getattribute__implementations are encouraged to be as small as simple as possible.- It is considered wrong for a
__getattribute__implementation to behave differently based on how/why it was called. - Code accessing the attribute can just as well
try/exceptinstead of usinghasattr.
TL;DR: Nevertheless, I'd like to detect whether a call to __getattribute__ was done due to hasattr (verses a "genuine" attempt at accessing the attribute).
This is not possible, even through stack inspection.
hasattrproduces no frame object in the Python call stack, as it is written in C, and trying to inspect the last Python frame to guess whether it's suspended in the middle of ahasattrcall is prone to all kinds of false negatives and false positives.If you're absolutely determined to make your best shot at it anyway, the most reliable (but still fragile) kludge I can think of is to monkey-patch
builtins.hasattrwith a Python function that does produce a Python stack frame:Calling
probably_called_from_hasattrinside__getattribute__will then test if your__getattribute__was probably called fromhasattr. This avoids any need to assume that the calling code used the name "hasattr", or that use of the name "hasattr" corresponds to this particular__getattribute__call, or that thehasattrcall originated inside Python-level code instead of C.The primary sources of fragility here are if someone saved a reference to the real
hasattrbefore the monkey-patch went through, or if someone else monkey-patcheshasattr(such as if someone copy-pastes this code into another file in the same program). Theisinstancecheck attempts to catch most cases of someone else monkey-patchinghasattrbefore us, but it's not perfect.Additionally, if
hasattron an object written in C triggers attribute access on your object, that will look like your__getattribute__was called fromhasattr. This is the most likely way to get false positives; everything in the previous paragraph would give false negatives. You can protect against that by checking that the entry forobjin thehasattrframe'sf_localsis the object it should be.Finally, if your
__getattribute__was called from a decorator-created wrapper, subclass__getattribute__, or something similar, that will not count as a call fromhasattr, even if the wrapper or override was called fromhasattr, even if you want it to count.