2
>>> i = 1
>>> A = [3,4,-1,1]
>>> A[A[i] - 1], A[i]  =  A[i], A[A[i] - 1]
>>> A
[3, 1, -1, 4]
>>> A = [3,4,-1,1]
>>> A[i], A[A[i] - 1] = A[A[i] - 1], A[i]
>>> A
[4, 1, -1, 1]

I have question when I do assignment for multiple variables for a list. Like the example above, the assignment

A[A[i] - 1], A[i]  =  A[i], A[A[i] - 1]

is different from the assignment

A[i], A[A[i] - 1] = A[A[i] - 1], A[i]

I am really confused the inside calculation order in Python. Why the results are different? What's the best way to do this kind of multiple assignment in one line?

Mian
  • 61
  • 5
  • @Prune: None of the answers there discuss the kind of interference between assignments that shows up in this question. – user2357112 Oct 26 '16 at 21:55
  • 2
    I just broke it down to the same logical sequence, and reproduced the user's problem. In short, I *did* get the same interactions. – Prune Oct 26 '16 at 22:03
  • @Prune: Are you seeing something in those answers that I'm not? None of them say anything about how assignments and evaluations are interleaved on the LHS, or how that can lead to assignments interfering with each other. – user2357112 Oct 26 '16 at 22:18
  • Yes. This is the same process as outlined in the accepted answer, which devolves from the documentation you quoted, and the examples we have in our answers to this question. – Prune Oct 26 '16 at 22:24
  • @Prune: The accepted answer doesn't say anything about how expressions on the LHS are handled. None of the assignment targets in the question or any answer involve expression evaluations. Reading and fully comprehending those answers would still not provide enough information to determine what happens in this question's case. – user2357112 Oct 26 '16 at 22:29
  • 1
    Related: [Python Assignment Operator Precedence - (a, b) = a\[b\] = {}, 5](http://stackoverflow.com/q/32127908) – Martijn Pieters Oct 26 '16 at 22:31
  • @Prune: The answers say that the right-hand side is evaluated before any assignments are performed, but they don't say anything about evaluating the left-hand side. From the information in those answers, it would be impossible to determine whether the `A[i] - 1` on the LHS in `A[i], A[A[i] - 1] = A[A[i] - 1], A[i]` is evaluated before the RHS, before the first assignment, or before the second assignment. – user2357112 Oct 26 '16 at 22:35
  • 1
    @BhargavRao: Yeah, that's a much better dupe. – user2357112 Oct 27 '16 at 21:21

3 Answers3

4

Per the documentation:

Python evaluates expressions from left to right. Notice that while evaluating an assignment, the right-hand side is evaluated before the left-hand side.

For more detail, see this section. There is a good example of a brain-teaser exploiting this behaviour here.

This means that the right-hand side of the = is evaluated first, left-to-right, then the assignment is made to the left-hand side, left-to-right. Necessarily, the brackets are evaluated inside-out. Breaking that down stepwise, here's the first example:

i = 1
A = [3, 4, -1, 1]

A[A[i] - 1], A[i] = A[i], A[A[i] - 1]
                  = A[1], A[A[i] - 1]
                  = 4, A[A[i] - 1]
                  = 4, A[A[1] - 1]
                  = 4, A[4 - 1]
                  = 4, A[3]
                  = 4, 1
A[A[i] - 1], A[i] = 4, 1
A[A[1] - 1], A[i] = 4, 1
A[4 - 1], A[i] = 4, 1
A[3], A[i] = 4, 1  # A becomes [3, 4, -1, 4]
A[i] = 1
A[1] = 1  # A becomes [3, 1, -1, 4]

And here's the second:

i = 1
A = [3, 4, -1, 1]

A[i], A[A[i] - 1] = A[A[i] - 1], A[i]
                  = A[A[1] - 1], A[i]
                  = A[4 - 1], A[i]
                  = A[3], A[i]
                  = 1, A[i]
                  = 1, A[1]
                  = 1, 4
A[i], A[A[i] - 1] = 1, 4
A[1], A[A[i] - 1] = 1, 4  # A becomes [3, 1, -1, 1]
A[A[1] - 1] = 4
A[1 - 1] = 4
A[0] = 4  # A becomes [4, 1, -1, 1]

The assignment to the left-hand target on the left-hand side alters the content of A, which changes the indexing in the right-hand target. The 4 is assigned to either A[3] or A[0], depending on the value of A[1] (which changes from 4 to 1) when the index is calculated.

"What's the best way to do this kind of multiple assignment in one line?" - I'd do my very best to avoid it. I can't think of any situation where it would be necessary to assign to moving targets like this.

Community
  • 1
  • 1
jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
  • If we could write A.swap(i,A[i]-1) then a one-liner would do... Maybe the reason why there is no such swap function is precisely this multiple assignment feature... like presumed at https://www.reddit.com/r/Python/comments/3eh5p3/why_isnt_there_a_listswapi_j_function_built_in_to/ – aka.nice Oct 26 '16 at 22:33
  • @aka.nice then `A[i] - 1` would be evaluated before the function was called, and be fixed on both sides of the assignment – jonrsharpe Oct 26 '16 at 22:35
  • Yes, implicitely pushing A[i]-1 in a temp (or stack) is precisely what would make the expression work symmetrically A.swap(A[i]-1,i) would have the same effect – aka.nice Oct 26 '16 at 22:40
1

With how you're encouraged to do things like a, b = b, a+b, you might think that multiple assignments like this are supposed to always bypass problems with one assignment interfering with another. Particularly, it's natural to think that all expression evaluations happen before any assignments. Unfortunately, that's not how it works.

When you have a multiple assignment of the form

expr_a[expr_b], expr_c[expr_d] = expr_e, expr_f

the order of events goes as follows:

  1. Evaluate the right-hand side, evaluating expr_e and expr_f.
  2. Perform the first assignment, evaluating expr_a and expr_b.
  3. Perform the third assignment, evaluating expr_c and expr_d. The first assignment has already happened when these expressions are evaluated.

That means that if assigning to expr_a[expr_b] changes the value of expr_c or expr_d, that will change what happens in the second assignment.

In your case, assigning to A[i] changes the value of A[i] - 1 in the assignment target A[A[i] - 1].


Don't use a multiple assignment in cases like this. Separate the assignments onto their own lines, and use temporary variables if necessary to remember values that get changed by assignments.

user2357112
  • 260,549
  • 28
  • 431
  • 505
0

The sequence of operations works the same for any multiple assignment:

  1. Evaluate every expression on the RHS (right-hand side), left to right, placing each into a temporary variable.
  2. Evaluate the expressions on the LHS (left), left to right. For each of these, assign the corresponding temporary variable from the RHS.

For your code, this expands to:

# A[A[i] - 1], A[i]  =  A[i], A[A[i] - 1]
A = [3,4,-1,1]
t1 = A[i]
t2 = A[A[i] - 1]
# t1 = 4, t2 = 1
A[A[i] - 1] = t1
A[i] = t2
print A
# Result: [3, 1, -1, 4]

# A[i], A[A[i] - 1] = A[A[i] - 1], A[i]
A = [3,4,-1,1]
t2 = A[A[i] - 1]
t1 = A[i]
# As before, t1 = 4, t2 = 1
A[i] = t2
# A[i] is now 1 !
A[A[i] - 1] = t1
print A
# Result: [4, 1, -1, 1]

The critical difference is when A[i] gets changed in each sequence. IN the second example, when we evaluate A[A[i] - 1] in the final assignment, A[i] has already changed to 1. Therefore, this last assignment puts 4 into location 0, rather than location 3.

Prune
  • 76,765
  • 14
  • 60
  • 81