Don’t use Python’s
hasattr() unless you’re writing Python 3-only code and understand how it works.
hasattr() is a common topic in Python code reviews. So instead of producing gists for each one, here once and for all:
if hasattr(x, "y"): print(x.y) else: print("no y!")
try: print(x.y) except AttributeError: print("no y!")
y = getattr(x, "y", None) if y is not None: print(y) else: print("no y!")
Especially, if you’re handling classes that aren’t your own.
hasattr() also is not faster than
getattr() since it goes through the exactly same lookup process and then throws away the result2.
It might seem similar and the extra lines is annoying, but using
hasattr() on Python 2 is close to writing:
try: print(x.y) except: print("no y!")
Which is almost never what you want because it shadows errors in properties:
>>> class C(object): ... @property ... def y(self): ... 0/0 ... >>> hasattr(C(), "y") False
Since in third party classes you can’t know whether an attribute is a property (or becomes one through an update after months or years), this is dangerous.
Do you think that’s no big deal? Check out this glowing testimonial! Another visible implication is that using
hasattr() on properties does execute their getter function which makes the name misleading.
Python 3 gets it right3:
>>> class C: ... @property ... def y(self): ... 0/0 ... >>> hasattr(C(), "y") Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 4, in y ZeroDivisionError: division by zero
Which makes it another edge case when writing hybrid code for both Python 2 and 3. But, would you expect
hasattr() to raise that error?
The observant reader might ask, what about
AttributeErrors? And indeed: there’s no way to distinguish an
AttributeError that is caused by a missing attribute and an
AttributeError that is caused by a buggy property. The outlined approaches reduce your possible errors to only that one and avoid confusing differences in behavior between Python 2 and 3.
For your own code, you could still use
hasattr() of course. But you’d have to keep track of it, remember to fix it if your classes change. This all adds unnecessary mental burden just to save some typing.
P.S. Yeah the title changed. Originally I really wrote this article so I can point people to it in discussions. I never expected it to hit #1 on Hacker News and I agree that “Considered Harmful” is getting old.
I just didn’t spend too much time worrying about it while hacking it together between getting up and leaving for work; it simply was the first thing that popped into my mind. I didn’t rename it right away as it may have confused people but now the dust has settled, it’s time to move on.
getattr()example presumes the absence of the attribute is equivalent its value being
None(which is common). Use a sentinel value if you need to differentiate those two cases. ↩︎
- At least on CPython. ↩︎
- Some readers got really mad that I dared to criticize a function that’s fine in Python 3. Because nobody should write Python 2 anymore. Although I’m not a Python 3-hating Armin, one has to acknowledge that most Python code written today is either Python 2 or hybrid. And as someone maintaining many hybrid libraries, I find the difference in behavior between 2 and 3 even worse than the behavior on 2 by itself as it leads to nasty surprises. So chill out, have a cocoa, and port some library. ↩︎