Some Python fun: what does the following code print? And why?
import os
class A:
@property
def meth0(self):
return None
@property
def meth1(self):
return os.whoops
@property
def meth2(self):
return whoops
a = A()
print(hasattr(a, 'meth0'))
print(hasattr(a, 'meth1'))
print(hasattr(a, 'meth2'))
The answer could be a little irritating:
True
False
Traceback (most recent call last):
File "test.py", line 19, in <module>
print(hasattr(a, 'meth2'))
File "test.py", line 14, in meth2
return whoops
NameError: name 'whoops' is not defined
It would be nice (or perhaps less surprising) if the output were instead:
True
True
True
So what’s happening here? A combination of things:
hasattrtests for the presence of an attribute by callinggetattron the attribute name, and catchingAttributeError.- When
getattris called formeth1it throws anAttributeError– not becausemeth1doesn’t exist, but becauseos.whoopsdoesn’t – this propagates until it is caught byhasattr, which then concludes that the object has no methodmeth1. - When
getattris called formeth2, aNameErroris thrown instead, which isn’t caught byhasattr, so we observe a different behaviour that can appear “inconsistent” with the behaviour formeth1.
Sometimes the behaviour exhibited with meth1 can mask an AttributeError due to a bug in the code for a property. For example:
import socket
class Data:
def __init__(self, payload):
self.payload = payload
class Packet(Data):
@property
def ip_address(self):
hostname = socket.gethostname()
# Type: should be `gethostbyname`
return socket.getbyname(hostname)
class Frame(Data):
@property
def ethernet_address(self):
return "00:11:22:33:44:55"
# Create a packet
p = Packet("HELLO WORLD")
# Handle p based on whether it is a Frame or a Packet
if hasattr(p, 'ip_address'):
print("Packet with payload '%s'" % p.payload)
else:
print("Frame with payload '%s'" % p.payload)
The output is, (maybe) surprisingly:
Frame with payload 'HELLO WORLD'
p is actually a Packet but it is mistreated as a Frame – in this straight-line, rather contrived example, it appears clear what’s happening. In the context of a larger program where the code is spread across multiple modules and functions along with other code, tracing the source of such a problem can be a little more time consuming and confusing – I encountered exactly this issue recently when debugging an issue in Numba, which left enough of an impression on me to motivate this post!