python dict setdefault, confused
Read the documentation for dict.setdefault
: it is like get
but if the key wasn't present then it is also set:
>>> my_dict = {}
>>> my_dict.setdefault('some key', 'a value')
'a value'
>>> my_dict
{'some key': 'a value'}
>>> my_dict.get('some key2', 'a value2')
'a value2'
>>> my_dict
{'some key': 'a value'}
Modifying a little your example:
>>> def what(*words):
... d = dict()
... for word in words:
... curr = d
... for letter in word:
... curr = curr.setdefault(letter, {})
... curr = curr.setdefault('.', '.')
... print 'curr is now: %r while d is %r' % (curr, d)
...
>>> what('foo')
curr is now: '.' while d is {'f': {'o': {'o': {'.': '.'}}}}
As you can see curr
changes, because when calling setdefault
it sometimes(in your example always) create a new dict
and set it as value to curr
, while d
always refers to the original dict
. As you can see it is modified after the loop, since it's value is {'f': {'o': {'o': {'.': '.'}}}}
which is quite different from {}
.
Probably your confusion is due to the fact that curr = curr.setdefault(letter, {})
always create a new and empty dict
, which is then assigned to curr
(and thus for every letter you add a nesting level to the original dict
instead of overwriting the values).
See this:
>>> my_dict = {}
>>> curr = my_dict
>>> for letter in 'foo':
... print 'my_dict is now %r. curr is now %r' % (my_dict, curr)
... curr = curr.setdefault(letter, {})
...
my_dict is now {}. curr is now {}
my_dict is now {'f': {}}. curr is now {}
my_dict is now {'f': {'o': {}}}. curr is now {}
>>> my_dict
{'f': {'o': {'o': {}}}}
As you can see for every level the my_dict
has a new nesting level.
Maybe, but I'm just guessing, you wanted to obtain something like 'foo' -> {'f': {}, 'o': {}}
, in which case you should do:
>>> my_dict = {}
>>> for letter in 'foo':
... my_dict.setdefault(letter, {})
...
>>> my_dict
{'o': {}, 'f': {}}
d = dict()
--> initializes an empty dictionary and binds it to the name d
; so you have a dictionary object ({}
) referenced by name d
Inside the outer for loopcurr = d
--> binds another name curr
to the same object. So, names (d
and curr
refer to the same object)
Inside the inner for loop
During the first iteration letter = 'f'
curr = curr.setdefault(letter, {})
There are 2 things that are happening in the above statement,
A) curr.setdefault(letter, {})
--> As per documentation:
"If key is in the dictionary, return its value. If not, insert key with a value of default and return default. default defaults to None.".
Since, the letter 'f' is not in the initial dictionary object it mutates the initial object to {'f':{}}
and returns the value {}
, which is not the initial dictionary object, but a new one that was created because of the setdefault statement. At this time both curr
and d
refer to the initial dictionary object which has since mutated to {'f':{}}
.
B) Reassignment of the name curr
to the return value mentioned above. Now, the names curr
and d
refer to different objects. d
refers to the object {'f':{}}
, while curr
refers to an empty dictionary object, which is actually the value of d['f']
.
This is why the nesting happens in the original dictionary object, as we go through the loop.