Why does a = a['k'] = {} create an infinitely nested dictionary?
Asked Answered
J

1

10

Here is my Python code that creates an infinitely nested dictionary:

a = a['k'] = {}

print(a)
print(a['k'])
print(a['k']['k'])
print(a is a['k'])

Here is the output:

{'k': {...}}
{'k': {...}}
{'k': {...}}
True

The output shows that a['k'] refers to a itself which makes it infinitely nested.

I am guessing that the statement:

a = a['k'] = {}

is behaving like:

new = {}
a = new
a['k'] = new

which would indeed create an infinitely nested dictionary.

I looked at Section 7.2: Assignment statements of The Python Language Reference but I couldn't find anything that implies that a = a['k'] = {} should first set a to the new dictionary and then insert a key/value pair in that dictionary. Here are some excerpts from the reference that I found relevant but did not answer my question:

If the target list is a single target with no trailing comma, optionally in parentheses, the object is assigned to that target.

If the target is a subscription: The primary expression in the reference is evaluated. It should yield either a mutable sequence object (such as a list) or a mapping object (such as a dictionary). Next, the subscript expression is evaluated.

If the primary is a mapping object (such as a dictionary), the subscript must have a type compatible with the mapping’s key type, and the mapping is then asked to create a key/datum pair which maps the subscript to the assigned object. This can either replace an existing key/value pair with the same key value, or insert a new key/value pair (if no key with the same value existed).

Each of these excerpts define the behaviour of an assignment with a single target such as a = {} and a['k'] = {} but they don't seem to talk about what should happen in case of a = a['k'] = {}. Where is the order of evaluation for such a statement documented?


This question is now resolved. GPhilo's answer pointed to the relevant clause of Section 7.2: Assignment statements. The relevant clause was right at the beginning but I had overlooked it earlier. Here it is:

An assignment statement evaluates the expression list (remember that this can be a single expression or a comma-separated list, the latter yielding a tuple) and assigns the single resulting object to each of the target lists, from left to right.

Let us compare it with the grammar now.

The assignment statement is defined as

assignment_stmt ::=  (target_list "=")+ (starred_expression | yield_expression)

So the statement

a = a['k'] = {}

has two target_list elements, i.e., a and a['k'], and a starred_expression element, i.e., {}, so {} is assigned to each of the target lists a and a['k'] from left to right.

Judsonjudus answered 21/2, 2019 at 13:37 Comment(0)
H
10

Assignments in an assignment statement are resolved from left to right, as per the section 7.2 you quoted (emphasis mine):

An assignment statement evaluates the expression list (remember that this can be a single expression or a comma-separated list, the latter yielding a tuple) and assigns the single resulting object to each of the target lists, from left to right.

That means that yes, indeed your statement is equivalent to:

new = {}
a = new
a['k'] = new

As a quick counter-proof, swapping the order of the assignments results in error:

a['k'] = a = {}

raises

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
Hollinger answered 21/2, 2019 at 13:44 Comment(4)
Thank you. This makes sense. Assignment statement is defined as assignment_stmt ::= (target_list "=")+ (starred_expression | yield_expression), so a = a['k'] = {} has two target_list elements (a and a['k']) and a starred_expression element ({}), so {} is assigned to each of the target lists a and a['k'] from left to right.Judsonjudus
I still don't understand. If resulting object to each of the target lists, from left to right is the case, then a should be equal to {'k': {}} and a['k'] should return in {}, right? I am not able to understand why infinite nesting takes place here.Esdras
No, first the expression on the rightmost side is evaluated (which is the right-side of the assignment), so {} is defined. Then, the list of expressions on the left side of the assignment is evaluated, one by one, from left to right. After each evaluation, the assignment happens, before moving on to the next evaluation. Thus, the first ting evaluated is 1, which is just a name, so it's immediately assigned (hence, at this point, a == {}). Then, the second expression is evaluated: a['k'] is now valid, so the assignment can go through (a['k']=={}). The object assigned, however, ...Hollinger
... is the same, so a is a['k'].Hollinger

© 2022 - 2024 — McMap. All rights reserved.