I have a class that overloads object attribute access by returning the attributes of its “row” attribute, along the following lines:
from collections import namedtuple
class MyObj(object):
def __init__(self, y, z):
r = namedtuple('row', 'a b')
self.row = r(y, z)
self.arbitrary = True
def __getattr__(self, attr):
return getattr(self.row, attr)
def __dir__(self):
return list(self.row._fields)
In [2]: m = MyObj(1, 2)
In [3]: dir(m)
Out[3]: ['a', 'b']
In [4]: m.a
Out[4]: 1
In [5]: vars(m)
Out[5]: {'arbitrary': True, 'row': row(a=1, b=2)}
In [6]: output = '{a} -> {b}'
In [7]: output.format(**vars(m.row))
Out[7]: '1 -> 2'
In [8]: output.format(**vars(m))
KeyError: 'a'
As I quite often do string formatting using vars() I’d like to be able to access row’s attributes directly from the call to vars(). Is this possible?
Edit following aaronsterling’s answer
The key to solving this, thanks to Aaron’s pointer, is to check for __dict__ in __getattribute__
from collections import namedtuple
class MyObj(object):
def __init__(self, y, z):
r = namedtuple('row', 'a b')
self.row = r(y, z)
self.arbitrary = True
def __getattr__(self, attr):
return getattr(self.row, attr)
def __getattribute__(self, attribute):
if attribute == '__dict__':
return self.row._as_dict()
else:
return object.__getattribute__(self, attribute)
def __dir__(self):
return list(self.row._fields)
In [75]: m = MyObj(3, 4)
In [76]: m.a
Out[76]: 3
In [77]: vars(m)
Out[77]: OrderedDict([('a', 3), ('b', 4)])
The docs are so kind as to specify that
varsworks by returning the__dict__attribute of the object it’s called on. Hence overriding__getattribute__does the trick. YI subclass
dict(but see later) to override the__str__function. The subclass accepts a functionstr_funcwhich gets called to return a string representation of how you want your__dict__object to appear. It does this by constructing a regular dictionary with the entries that you want and then callingstron that.This is very hacky. In particular, it will break any code that depends on doing anything like
This code will now update a phantom dictionary and not the real one.
A much more robust solution would depend on completely replacing all methods that set values on the
SpoofedDictwith methods that actually updatemyobj.__dict__. This would requireSpoofedDictinstances to hold a reference tomyobj.__dict__. Then of course, the methods that read values would have to fetch them out ofmyobj.__dict__as well.At that point, you’re better off using
collections.Mappingto construct a custom class rather than subclassing fromdict.Here’s the proof of concept code, hackish as it may be: