Reputation: 1384
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)])
Upvotes: 5
Views: 2430
Reputation: 3012
You can also define a @property
for __dict__
instead of overriding __getattribute__
.
class Example:
@property
def __dict__(self):
return "From vars()"
e = Example()
print(vars(e)) # From vars()
Upvotes: 4
Reputation: 71054
The docs are so kind as to specify that vars
works by returning the __dict__
attribute of the object it's called on. Hence overriding __getattribute__
does the trick. Y
I subclass dict
(but see later) to override the __str__
function. The subclass accepts a function str_func
which 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 calling str
on that.
This is very hacky. In particular, it will break any code that depends on doing anything like
myobj.__dict__[foo] = bar
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 SpoofedDict
with methods that actually update myobj.__dict__
. This would require SpoofedDict
instances to hold a reference to myobj.__dict__
. Then of course, the methods that read values would have to fetch them out of myobj.__dict__
as well.
At that point, you're better off using collections.Mapping
to construct a custom class rather than subclassing from dict
.
Here's the proof of concept code, hackish as it may be:
from collections import namedtuple
class SpoofDict(dict):
def __init__(self, *args, **kwargs):
self.str_func = kwargs['str_func']
del kwargs['str_func']
dict.__init__(self, *args, **kwargs)
def __str__(self):
return self.str_func()
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)
def str_func(self):
attrs = list(self.row._fields)
str_dict = {}
row = object.__getattribute__(self, 'row')
for attr in attrs:
str_dict[attr] = getattr(row, attr)
return str(str_dict)
def __getattribute__(self, attribute):
if attribute == '__dict__':
spoof_dict = SpoofDict(str_func=self.str_func)
spoof_dict.update(object.__getattribute__(self, '__dict__'))
return spoof_dict
else:
return object.__getattribute__(self, attribute)
if __name__=='__main__':
m = MyObj(1, 2)
print "dir(m) = {0}".format(dir(m))
print "vars(m) = {0}".format(vars(m))
print "m.row = {0}".format(m.row)
print "m.arbitrary = {0}".format(m.arbitrary)
Upvotes: 2