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