Lukas Lukas - 4 months ago 11
Python Question

Can a class handle AttributeError by itself using some meta magic?

So I have this example code, where Foo is very dynamic data structure.

def fetch():
# simulate api request
return {'param1':1, 'param2':{'description':'param2', 'value':2}}

class Foo(object):
def __init__(self):
self._rawdata = fetch()

#set attributes accordingly to the downloaded data
for key, val in self._rawdata.items():
setattr(self, key, val)

def __str__(self):
# just an example
out = ''
for key, val in self._rawdata.items():
out += key + ': ' + str(val) + '\n'
return out


A user might want to try do this:

f = Foo()
print(f.param3)


The user doesn't know whether the 'param3' exists or not (API might not have any data avaliable, in which case it won't provide the key at all)
Naturally, this will result in AttributeError being raised.

print(f.param3)
AttributeError: 'Foo' object has no attribute 'param3'


My question is this: Is there some metaprogramming magic way to wrap the Foo() class into something that will make 'f.nonexistent_attribute' return 'None' instead of traceback? I would really like to avoid hardcoding expected properties (what if API changes?).

Answer

Implement the __getattr__ method; it'll be called for all nonexistent attributes:

def __getattr__(self, name):
    # all non-existing attributes produce None
    return None

From the documentation:

Called when an attribute lookup has not found the attribute in the usual places (i.e. it is not an instance attribute nor is it found in the class tree for self). name is the attribute name. This method should return the (computed) attribute value or raise an AttributeError exception.

Demo:

>>> def fetch():
...     # simulate api request
...     return {'param1':1, 'param2':{'description':'param2', 'value':2}}
...
>>> class Foo(object):
...     def __init__(self):
...         self._rawdata = fetch()
...         #set attributes accordingly to the downloaded data
...         for key, val in self._rawdata.items():
...             setattr(self, key, val)
...     def __getattr__(self, name):
...         # all non-existing attributes produce None
...         return None
...
>>> print(f.param1)
1
>>> f = Foo()
>>> print(f.param3)
None