0

I have the following piece of code:

stack = [1, 2, 3]
lst = [[0 for _ in range(5)] for _ in range(5)]      # Just a 5x5 2D list of 0

The following assignment didn't behave like I expected lst[stack.pop()][stack.pop()] = stack.pop() I thought the above would be lst[3][2] = 1. Turn out it's lst[2][1] = 3 I kinda have a vague idea why, but I do want to know more about this behavior. Can you guys point me to any doc or article about this?

NepNep
  • 274
  • 1
  • 2
  • 6

2 Answers2

4

This has to do with the execution order of expressions. The right side gets evaluated first, and then the two other ones from left to right. The right side usually gets evaluated first in almost all programming languages.

The relevant section of the Python documentation is here: Python Reference Assignment Statements. Stated there 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.

Based on this definition of assignment statements:

assignment_stmt ::=  (target_list "=")+ (starred_expression | yield_expression)
target_list     ::=  target ("," target)* [","]
target          ::=  identifier
                     | "(" [target_list] ")"
                     | "[" [target_list] "]"
                     | attributeref
                     | subscription
                     | slicing
                     | "*" target

There are more rules at play that really define the execution order, but that gets deep into the specification and into technical details how the language works in every possible situation. But above is the answer to your specific question.

SkryptX
  • 813
  • 1
  • 9
  • 24
  • 1
    Just throwing this link here if someone want's to read more on evaluation order. https://docs.python.org/3/reference/expressions.html#evaluation-order – Sri May 24 '20 at 19:48
  • 1
    @Sri Nice, there is the simpler version of my answer in one sentence :) – SkryptX May 24 '20 at 19:56
1

Let's see what the disassembler can tell us:

>>> dis.dis('lst[stack.pop()][stack.pop()] = stack.pop()')
  1           0 LOAD_NAME                0 (stack)
              2 LOAD_METHOD              1 (pop)
              4 CALL_METHOD              0
              6 LOAD_NAME                2 (lst)
              8 LOAD_NAME                0 (stack)
             10 LOAD_METHOD              1 (pop)
             12 CALL_METHOD              0
             14 BINARY_SUBSCR
             16 LOAD_NAME                0 (stack)
             18 LOAD_METHOD              1 (pop)
             20 CALL_METHOD              0
             22 STORE_SUBSCR
             24 LOAD_CONST               0 (None)
             26 RETURN_VALUE

The assignment operation is broken down like this:

lst[b][c] = a

In terms of methods being called, this is equivalent to

lst.__getitem__(b).__setitem__(c, a)

(with the call to __getitem__ used by BINARY_SUBSCR and the call to __setitem__ by STORE_SUBSCR).

Although it would see that b should have been evaluated first, assignments are always executed by evaluating the right-hand side first, then the left-hand side, so the real execution is closer to

t1 = a
t2 = lst.__getitem__(b)
t2.__setitem__(c, t1)
chepner
  • 497,756
  • 71
  • 530
  • 681