using __getitem()__ correctly

Oscar Benjamin oscar.j.benjamin at gmail.com
Thu Dec 31 07:12:43 EST 2015


On 31 December 2015 at 11:30, Charles T. Smith
<cts.private.yahoo at gmail.com> wrote:
>>> Obviously there is a syntax difference between x.attr and x['key']
>>
>> Not merely syntax; the attributes of an object are not generally
>> available as items of the container.
>
>
> What are the set of ways that an attribute is accessible?  Including
> implementation implications?
>
>
>>
>>> Either the instance __dict__, the class __dict__, or a superclass
>>> __dict__.
>>
>> No, I'm not referring to the ‘__dict__’ attribute of an object; I'm
>> referring to the object itself.
>>
>> To talk about the attributes of an object ‘foo’ is distinct from talking
>> about the items in a dictionary ‘foo’. That distinction is real, and
>> important.
>
>
> But wanting to deal with the attributes of an object without considering
> the way it's implemented - although possible - requires a complete virtual
> model that covers all implications.  It's easier to simply understand how the
> objects work under the covers.

When you write x.attr the name 'attr' is looked up on the object x.
This calls x.__getattribute__('attr'). In turn this checks the dict
associated with the object x i.e. x.__dict__['attr']. This in turn
calls x.__dict__.__getitem__('attr'). The lookup of x.__dict__ is
special and doesn't use the normal __getattribute__ mechanism
(otherwise this would be an infinite recursion). Generally special
attributes (with double underscores) are looked up in a different way.
If x.__dict__ does not have the attribute then the dict associated
with the class/type of x is checked i.e. x.__class__.__dict__['attr'].
The lookup of x.__class__ is also special. Failing this the other
classes in x.__class__.__mro__ are checked i.e.
x.__class__.__mro__[1].__dict__['attr']. Once these are exhausted
x.__getattribute__('attr') falls back on calling
x.__getattr__('attr').

IIUC you're trying to create an attribute dict where the same
attributes can be looked up via x.attr or x['attr'] (i.e.
x.__getattribute__('attr') or x.__getitem__('attr')). One way to do
this is to subclass dict and then set each instances __dict__ to be
itself. This way __getattribute__ will search the instance (which is a
dict subclass) as its own attribute dict. You can then use __getattr__
as the fallback for attributes that are not found. A simple
implementation of this could look like:

class attrdict(dict):
    def __init__(self, name=None):
        self.__dict__ = self
        kwargs = {'name':name} if name is not None else {}
        super(attrdict, self).__init__(**kwargs)
    def __getattr__(self, attrname):
        ob = self[attrname] = type(self)(name=attrname)
        return ob

>>> d = attrdict('foo')
>>> d
{'name': 'foo'}
>>> d.bar
{'name': 'bar'}
>>> d
{'bar': {'name': 'bar'}, 'name': 'foo'}

The problem with this as with any dict subclass approach is that a
dict has a load of methods (.items() .keys() .pop() ...) that will now
become conflated with your dict keys when you use the attribute
access:

>>> d.items
<built-in method items of attrdict object at 0x7f2025eab868>

This means that you can't use any of the dict method names in whatever
you're doing. This is the reason that a dict uses a different
mechanism __getitem__ so that it can store arbitrary keys at the same
time as having named methods which must be attributes. Personally I
think that the best approach is to ditch the idea of conflating
attributes and keys and just use the subscript x['attr'] syntax.

--
Oscar



More information about the Python-list mailing list