Newbie question about __getattr__ and __setattr__

Eric Jacobs x at x.x
Sat Oct 9 01:24:38 EDT 1999


Felix Thibault wrote:

... snip 2-level merger code
> 
> >>> import Dicky
> >>> i =Dicky.Nester({'a': {'b': {'c': 'd'}}})
> >>> i
> {'a': {'b': {'c': 'd'}}}
> >>> i = i+{'a':{'b':{'e':'f'}}}
> >>> i
> {'a': {'b': {'c': 'd', 'e': 'f'}}}
> >>>
> 
> I also wanted to be able to do this by assignment so I tried to
> make a __setattr__ method, but if I do:
> 
> i[1][2][3] = 4

A couple of things going on here. First, you want to deal with
__getitem__ and __setitem__ -- those deal with the [] subscript
operator -- rather than __getattr__ and __setattr__ that deal
with the . attribute operator.

The syntax that you want to write the assignment is supported
by the interpreter by default; you don't need to change 
anything for that. The only catch is that the first two layers
of dictionaries need to exist beforehand. So if you had

   i = {1: {2: {}}}
   i[1][2][3] = 4
 
then i would be {1: {2: {3: 4}}} as you'd expect.

So the easiest solution really would be to predefine all the
possible keys before you make any such assignment. If you
can't do that, the problem you're going to run in to is, 
as you mentioned, you're going to get a __getitem__ call
on a non-existent key, and you don't know whether it's part
of a read operation (and you should raise KeyError), or
it's part of a new assignment operation (and you should
create the sub-dictionaries.)

In other words, in the expression

   i[1][2][3]
   ^^^^^^^
this part is always going to be an r-value. If it's part
of an assignment, the [3] will be an l-value, and thus
__setitem__ will be called. Or if the whole expression
is an r-value, then the [3] will be an r-value and
__getitem__ will be called. But this won't help you
because the [1] and [2] will be __getitem__ in any case!

So if you want that assignment to work when the sub-
dictionaries aren't predefined, you can have your
__getitem__ always create new dictionaries. That's
fairly simple, just add a method:

	def __getitem__(s, k):
		if k not in s.keys():
			s[k] = Nester({})
		return s.data[k]

Be sure to mention the s.data[k] and not just s[k], or
you'll end up in an infinite loop. Here, if the key is not
found, we generate a new custom Nester object, so that it
works for more than one level. In fact, because there's no
limit placed here, this will work for any number of nested 
levels:
i['a']['b']['c']['d']['e'][...

The disadvantage is that reading a missing value will
result in a new empty dictionary being created and returned!
So unless you want to be really careful and write a clean-
up routine which can handle this, you're going to have a
bunch of extra empty dictionaries floating around. You will
also never get KeyError with this simple method.

It seems that the parser would know whether a compound
construct like i[1][2][3] is going to be an l-value or
r-value, and could emit a different code that could be
passed on to __getitem__ (BINARY_SUBSCRL, maybe, means
that this expression is intended to be an l-value.) It
would be difficult to make this change in a compatible
way. It's a thought..

Hope this helps.




More information about the Python-list mailing list