Peculiar Self-References
Peculiar Results
Here is a tiny Python example that creates a self-referential list and demonstrates the self-reference:
>>> a = a[0] = [0] >>> a [[...]] >>> a[0] [[...]] >>> a[0][0] [[...]] >>> a is a[0] True
The output shows that a[0]
refers to a
itself which makes it a self-referential list. Why does this simple
code create a self-referential list? Should it not have failed
with NameError
because a
is not yet
defined while assigning the list [0]
to a[0]
?
Here is another similar example that creates a self-referential list too:
>>> a = a[0] = [0, 0] >>> a [[...], 0]
Here is a similar example for dictionary:
>>> a = a[0] = {} >>> a {0: {...}}
Note that 0
is used as a dictionary key in the above
example. Here is another very simple example that uses a string
key:
>>> a = a['k'] = {} >>> a {'k': {...}}
The Language Reference
My first guess was that the statement
a = a[0] = [0]
behaves like
new = [0]
a = new
a[0] = new
which would indeed create a self-referential list.
Section 7.2 (Assignment statements) of The Python Language Reference confirms this behaviour. Quoting the relevant part from this section here:
Assignment statements are used to (re)bind names to values and to modify attributes or items of mutable objects:
assignment_stmt ::= (target_list "=")+ (starred_expression | yield_expression) target_list ::= target ("," target)* [","] target ::= identifier | "(" [target_list] ")" | "[" [target_list] "]" | attributeref | subscription | slicing | "*" target
(See section Primaries for the syntax definitions for attributeref, subscription, and slicing.)
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.
We see that the assignment statement is defined as follows:
assignment_stmt ::= (target_list "=")+ (starred_expression | yield_expression)
Thus the statement
a = a[0] = [0]
has two target_list
elements (a
and a[0]
) and a starred_expression
element
([0]
). As a result, the same list on the
right-hand-side is assigned to both a
and a[0]
, from left to right, i.e., the
list [0]
is first assigned to a
,
then a[0]
is set to the same list. As a
result, a[0]
is set to a
itself.
The behaviour of the statement
a = a[0] = {}
can be explained in a similar way. The dictionary object on the
right-hand-side is first assigned to a
. Then a
key 0
is inserted within the same dictionary.
Finally the value of a[0]
is set to the same
dictionary. In other words, a[0]
is set
to a
itself.
More Experiments
The evaluation of the expression list on the right hand side first
and then assigning the result to each target list from left to right
explains the behaviour we observed in the previous sections. This
left-to-right assignment is quite uncommon among mainstream
programming languages. For example, in C, C++, Java, and JavaScript
the simple assignment operator (=
) has right-to-left
associativity. The left-to-right assignment in Python can be
further demonstrated with some intentional errors. Here is an
example:
>>> a[0] = a = [0] Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'a' is not defined
In this example, when the assignment to a[0]
to occurs,
the variable named a
is not defined yet, so it leads
to NameError
.
Here is another example:
>>> a = a[0] = 0 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'int' object does not support item assignment
In this example, 0
is first assigned to a
.
Then a[0]
needs to be evaluated before 0
can be assigned to it but this evaluation fails
because a
is an int
, a type that does not
support
subscription
(also known as indexing), so it fails with TypeError
.