using __getitem()__ correctly

Ian Kelly ian.g.kelly at gmail.com
Wed Dec 30 15:40:44 EST 2015


On Wed, Dec 30, 2015 at 9:58 AM, Charles T. Smith
<cts.private.yahoo at gmail.com> wrote:
> On Wed, 30 Dec 2015 08:35:57 -0700, Ian Kelly wrote:
>
>> On Dec 30, 2015 7:46 AM, "Charles T. Smith"
>> <cts.private.yahoo at gmail.com> wrote:
>>> As is so often the case, in composing my answer to your question, I
>>> discovered a number of problems in my class (e.g. I was calling
>>> __getitem__() myself!), but I'm puzzled now how to proceed.  I thought
>>> the way you avoid triggering __getattr__() from within that was to use
>>> self.__dict__[name] but that doesn't work:
>>>
>>>   (PDB)p self.attrs.keys()
>>>   ['mcc', 'abc']
>>>   (PDB)p self.attrs.__dict__['abc']
>>>   *** KeyError: KeyError('abc',)
>>
>> What leads you to believe that this is triggering a call to __getattr__?
>> The KeyError probably just means that the key 'abc' wasn't found in the
>> dict.
>
>
> I meant, it doesn't work because I'm not getting at the attribute  Although keys()
> sees it, it's not in the __dict__ attribute of attrs.  If it's not there, where is it?

I think you're probably getting confused because there are three
different dicts at play here:

* Since your attrdict class inherits from dict, self.attrs is a dict.
* self.attrs.__dict__ is a *different* dict, used to store the
instance attributes of self.attrs.
* self.attrs.__class__.__dict__  is another different dict, used to
store the class attributes of attrdict.

The keys method that you're calling above is a method of the
self.attrs dict, which is where your attrdict's __setattr__ is setting
it. That's why you find it there but not in self.attrs.__dict__.

>>             print "attrdict:av:__getattr__: autovivifying ", name
>>             #self.__dict__.__setitem__ (name, self.__class__())
>>             #self.__setitem__ (name, self.__class__()) self.__setattr__
>>             (name, self.__class__())
>>
>> No reason to explicitly call __setitem__ or __setattr__ here. I'd
>> probably just do self[name] = self.__class__()
>
>
> The reason I used this is to avoid trigging the __setitem__() method:
>
>   self.__setattr__(name, self.__class__())
>
> which is invoked if I use the "self[name]" syntax.  But that didn't work.

But the body of your __setattr__ method is just "self[name] =
self.__class__()", which is the exact same code as what I suggested
and will still invoke __setitem__.

That said, I don't get why you're trying to avoid calling __setitem__.
If you're trying to store the attribute as a dict item, as you seem to
be doing, why shouldn't that dict's __setitem__ be involved?

> Is it just impossible to get at attributes without going through either
> __getattr__() or __getitem__()?

No.

>> Based on the preceding, you probably want to return the value you just
>> set in the dict, correct? So just return self[name].
>
>
> The problem is that then triggers the __getitem__() method and I don't
> know how to get to the attributes without triggering __getattr__().
>
> It's the interplay of the two that's killing me.

The only interplay of the two is what you have written into your class.

> In the example, if I have:
>
>   self.mcc = self.attrs.mcc
>
>
> The crux:
>
> Then __getattr__() triggers for the mcc.  If I try to use self.attrs['mcc']
> to get it, then that triggers __getitem__().  Okay, if the key is not an int,
> I'll go and get it and return it... unfortunately that triggers __getattr__(),
> an infinite loop.

How precisely are you trying to store these: as an attribute, or as a
dict item? If it's supposed to be in the dict, then why is your
__getitem__ trying to look up an attribute to begin with?

> class attrdict(dict):
>     def __init__ (self, name = None):
>         if name:
>             self.update (name)
>         print "attrdict: instantiated: ", name
>
>     # AutoVivification
>     def __getattr__ (self, name):
>         print "attrdict:av:__getattr__: entered for ", name
>         if name not in self.keys():
>             print "attrdict:av:__getattr__: autovivifying ", name
>             self[name] = self.__class__()
>         return self[name]
>
>     def __getitem__ (self, key):
>         print "attrdict:av:__getitem__: entered for ", key
>         if type (key) is int:          # TODO: support slices
>             return self.__getitem__(key)

Here the method as written is just going to end up calling itself. You
probably want super(attrdict, self).__getitem__(key)

>         return attrdict.__getattr__(self, key)

And here if you really want to access the instance attributes without
using __getattr__, just use self.__dict__[key].

I don't understand what it is that you're trying to accomplish here by
looking the key up in the instance attributes, though. It looks very
circular. I think you should clearly define where you expect the items
to be stored and then only check that location.



More information about the Python-list mailing list