Reputation: 2290
I want to write a container class which
data['a']
and attribute-like access e.g. data.a
; this is addressed herecollections.OrderedDict
; this is addressed hereI adapted the solution for 1. to subclass collections.OrderedDict
instead of dict
but it does not work; see below.
from collections import OrderedDict
class mydict(OrderedDict):
def __init__(self, *args, **kwargs):
super(mydict, self).__init__(*args, **kwargs)
self.__dict__ = self
D = mydict(a=1, b=2)
#D['c'] = 3 # fails
D.d = 4
#print D # fails
The two lines with the failure comment cause the following errors:
print D
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/collections.py", line 176, in __repr__
return '%s(%r)' % (self.__class__.__name__, self.items())
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/collections.py", line 113, in items
return [(key, self[key]) for key in self]
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/collections.py", line 76, in __iter__
root = self.__root
AttributeError: 'struct' object has no attribute '_OrderedDict__root'
and
D['c'] = 3
File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/collections.py", line 58, in __setitem__
root = self.__root
AttributeError: 'mydict' object has no attribute '_OrderedDict__root'
Is there a solution to this ? Can the __init__
function be patched ?
Upvotes: 4
Views: 4141
Reputation: 365925
First, if you just want to get this working, not understand what's wrong with your attempt, there are multiple "AttrDict" implementations on PyPI, ActiveState, etc. Search for one and look at its code—or just install it and use it.
That self.__dict__ = self
is not what you want. I realize that people recommend it, but it has some fundamental major problems. And one of those problems is exactly the one you're seeing.
In particular, it means you've now lost all of your existing attributes, including internal attributes used by OrderedDict
itself (like that __root
that you're getting errors about) and special attributes attached to all objects (like __class__
).
When you're just using dict
you don't notice this problem—at least in CPython—because dict
is implemented in C, and the special members that it needs to operate are stored in a C struct, not in the __dict__
, so you can't lose them. But for any class implemented in Python that needs any special members, the problem becomes visible.
Python has special methods for customizing attribute access. What you want to do is implement those methods:
class AttrDict(OrderedDict):
def __getattr__(self, name):
return self[name]
def __setattr__(self, name, value):
self[name] = value
def __delattr__(self, name):
del self[name]
However, in this case, even that won't work for everything, because you're still going to get your overrides called when the code in OrderedDict
tries to use __root
! To fix that you need to forward only some calls in these methods to your dict, not all of them.
And that "some" is complicated and hard to get right. A much simpler solution would be to just encapsulate and delegate to an OrderedDict
instead of inheriting:
class AttrDict(object):
def __init__(self, *args, **kwargs):
self._od = OrderedDict(*args, **kwargs)
def __getattr__(self, name):
return self._od[name]
def __setattr__(self, name, value):
if name == '_od':
self.__dict__['_od'] = value
else:
self._od[name] = value
def __delattr__(self, name):
del self._od[name]
Or, more simply, just use an OrderedDict as your __dict__
—which is the paradigm case for a metaclass, but if you want to do it quick&dirty, you can do it in __init__
or __new__
instead.
The latter solutions just make you into a normal object with attributes that happen to be ordered, not a mapping at all. But it's pretty easy to forward the mapping methods as well. Implement __getitem__
, __setitem__
, __delitem__
, __iter__
, and __len__
to forward to self._od
, and inherit from collections.MutableMapping
to fill in the rest of the API.
And now, if you go back and look up "AttrDict" on PyPI and click on the implementations, you'll see that this is exactly what they all do: they forward both __getattr__
and __getitem__
to an OrderedDict's __getitem__
, and so on.
Upvotes: 8