Why does hasattr execute the @property decorator code block
hasattr()
works by actually retrieving the attribute; if an exception is thrown hasattr()
returns False
. That's because that is the only reliable way of knowing if an attribute exists, since there are so many dynamic ways to inject attributes on Python objects (__getattr__
, __getattribute__
, property
objects, meta classes, etc.).
From the hasattr()
documentation:
This is implemented by calling
getattr(object, name)
and seeing whether it raises an exception or not.
If you don't want a property to be invoked when doing this, then don't use hasattr
. Use vars()
(which returns the instance dictionary) or dir()
(which gives you a list of names on the class as well).
hasattr
is basically implemented like this (except in C):
def hasattr(obj, attrname):
try:
getattr(obj, attname)
except AttributeError:
return False
return True
So in true "easier to ask for forgiveness than permission" (EAFP) fashion, to find out if an object has a given attribute, Python simply tries to get the attribute, and converts failure to a return value of False
. Since it's really getting the attribute in the success case, hasattr()
can trigger code for property
and other descriptors.
To check for an attribute without triggering descriptors, you can write your own hasattr
that traverses the object's method resolution order and checks to see whether the name is in each class's __dict__
(or __slots__
). Since this isn't attribute access, it won't trigger properties.
Conveniently, Python already has a way to walk the method resolution order and gather the names of attributes from an instance's classes: dir()
. A simple way to write such a method, then, would be:
# gingerly test whether an attribute exists, avoiding triggering descriptor code
def gentle_hasattr(obj, name):
return name in dir(obj) or hasattr(obj, name)
Note that we fall back to using hasattr()
if we can't find the desired name in dir()
, because dir()
won't find dynamic attributes (i.e., where __getattr__
is overridden). Code for these will still be triggered, of course, so if you don't care that you don't find them, you could omit the or
clause.
On balance, this is wasteful, since it gets all relevant attribute names when we're interested only in whether a specific one exists, but it'll do in a pinch. I'm not even sure that doing the loop yourself rather than calling dir()
would be faster on average, since it'll be in Python rather than in C.