50

I know about tuple unpacking but what is this assignment called where you have multiple equals signs on a single line? a la a = b = True

It always trips me up a bit especially when the RHS is mutable, but I'm having real trouble finding the right keywords to search for in the docs.

pfctdayelise
  • 5,115
  • 3
  • 32
  • 52
  • 6
    I would call them "[chained](http://en.wikipedia.org/wiki/Assignment_(computer_science)#Chained_assignment) [assignments](http://stackoverflow.com/questions/7601823/how-do-chained-assignments-work)." – senderle Jul 16 '12 at 05:16
  • Does this answer your question? [How do chained assignments work?](https://stackoverflow.com/questions/7601823/how-do-chained-assignments-work) – Basj Apr 15 '20 at 16:52

4 Answers4

74

It's a chain of assignments and the term used to describe it is...

- Could I get a drumroll please?

Chained Assignment.


I just gave it a quite google run and found that there isn't that much to read on the topic, probably since most people find it very straight-forward to use (and only the true geeks would like to know more about the topic).


In the previous expression the order of evaluation can be viewed as starting at the right-most = and then working towards the left, which would be equivalent of writing:

b = True
a = b

The above order is what most language describe an assignment-chain, but python does it differently. In python the expression is evaluated as this below equivalent, though it won't result in any other result than what is previously described.

temporary_expr_result = True

a = temporary_expr_result
b = temporary_expr_result

Further reading available here on stackoverflow:

Community
  • 1
  • 1
Filip Roséen - refp
  • 62,493
  • 20
  • 150
  • 196
  • 8
    It's calling this 'chained assignment' that is the real culprit. It'd be chained assignment if `a = b = c` meant that `b = c` is executed before `a = b`, but as you show that is not the case. Assignment takes place *sequentially* instead. – Martijn Pieters Apr 16 '13 at 19:51
  • 6
    This clarifies that in situations where like `x = dictionary['x'] = value` the dictionary does not use set then get (like would be done if it expanded to `dictionary['x'] = value; x = dictionary['x']`) but instead only sets the dictionary value. – coderforlife Jun 09 '15 at 02:19
14

@refp's answer is further supported with this output using the dis (disassembly) module:

>>> def a(x):
...   g = h = x
...
>>> import dis
>>> dis.dis(a)
  2           0 LOAD_FAST                0 (x)
              3 DUP_TOP
              4 STORE_FAST               1 (g)
              7 STORE_FAST               2 (h)
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE

The RHS is retrieved and duplicated, then stored into the destination variables left-to-right (try this yourself with e = f = g = h = x).

Some other posters have been confused if the RHS is a function call, like a = b = fn() - the RHS is only evaluated once, and then the result assigned to each successive variable. This may cause unwanted sharing if the returned value is a mutable, like a list or dict.

For those using threading, it is useful to note that there is no "atomicity" implied by the chained assignment form over multiple explicit assignment statements - a thread switch could occur between the assignments to g and h, and another thread looking at the two of them could see different values in the two variables.

From the documentation, 7.2. Assignment statements, g and h being two target lists, x being the expression list:

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

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.

Community
  • 1
  • 1
PaulMcG
  • 62,419
  • 16
  • 94
  • 130
9

OK, "chained assignment" was the search term I was after, but after a bit more digging I think it's not strictly correct. but it is easier to search for than "a special case of the assignment statement".

The Wikipedia article senderle linked to says:

In Python, assignment statements are not expressions and thus do not return a value. Instead, chained assignments are a series of statements with multiple targets for a single expression. The assignments are executed left-to-right so that i = arr[i] = f() evaluates the expression f(), then assigns the result to the leftmost target, i, and then assigns the same result to the next target, arr[i], using the new value of i.

Another blog post says:

In Python, assignment statements do not return a value. Chained assignment (or more precisely, code that looks like chained assignment statements) is recognized and supported as a special case of the assignment statement.

This seems the most correct to me, on a closer reading of the docs - in particular (target_list "=")+ - which also say

An assignment statement evaluates the expression list ... and assigns the single resulting object to each of the target lists, from left to right.

So it's not really "evaluated from right-most to left" - the RHS is evaluated and then assigned from left-most target to right - not that I can think of any real-world (or even contrived) examples where it would make a difference.

Community
  • 1
  • 1
pfctdayelise
  • 5,115
  • 3
  • 32
  • 52
  • my description is from the developers point of view, as you state yourself: there is no *real-world* scenario where the wording would result in different results. Or well, now I can actually think of one.. hold on, will update my answer with a little snippet to explain what I mean. – Filip Roséen - refp Jul 16 '12 at 06:54
  • nevermind, it won't make sense (and can't be done without a massive python hack, which is beyond the scope of the question), though I'll update my answer with some information from your post. – Filip Roséen - refp Jul 16 '12 at 06:57
  • please remember that this isn't like a normal forum where you can reply to previous posts with a new answer, if you have comments regarding contents in one post please use the "comment"-feature, quoting another answer in your post might cause trouble if one post is later deleted/altered. – Filip Roséen - refp Jul 16 '12 at 07:14
  • @refp I know how to use Stack Overflow. I wasn't replying to your answer, it prompted me to do some more digging and find my own answer. Last time I checked that was the right way to do things - http://blog.stackoverflow.com/2011/07/its-ok-to-ask-and-answer-your-own-questions/ – pfctdayelise Jul 16 '12 at 07:33
  • since you quoted *"evaluated from right-most to left"* it seemed like a reply, answering your own question is of course fine. – Filip Roséen - refp Jul 16 '12 at 07:34
  • 2
    Contrived example to show order can matter: `x = [0, 1, 2, 3]; x[0:2] = x[1:3] = [4];` results in `[4, 4]`, if you reverse them then it results in `[4, 3]`. – RemcoGerlich Jul 21 '16 at 14:36
4

got bitten by python's Chained Assignment today, due to my ignorance. in code

if l1.val <= l2.val:
    tail = tail.next = l1 # this line 
    l1 = l1.next

what I expected was

tail.next = l1
tail = tail.next
# or equivalently 
# tail = l1

whereas I got below, which produce a self loop in the list, leave me in a endless loop, whoops...

tail = l1
tail.next = l1 # now l1.next is changed to l1 itself

since for a = b = c, one way (python, for example) equivalent to

tmp = evaluate(c)
evaluate(a) = tmp
evaluate(b) = tmp

and have equal right operand for two assignment.

the other (C++, for example) equivalent to

evaluate(b) = evaluate(c)
evaluate(a) = evaluate(b)

since in this case a = b = c is basically

b = c
a = b

and two right hand operand could be different.

That why similar code works well in C++.

qeatzy
  • 1,363
  • 14
  • 21
  • Same! I think relying on this ordering is not pythonic however (I was interested in being "tricky" rather than sure it would work), any reader is not going to be 100% which way this goes. Better to be explicit in a case like this. – Andy Hayden Mar 14 '17 at 05:27
  • 1
    The pythonic way to do this would be to use tuple assignment/unpack (if I read your expected code correctly): `tail, tail.next = tail.next, l1`. The RHS gets fully evaluated, and then assigned left-to-right into the LHS tuple. – PaulMcG Jul 15 '19 at 12:28