How do chained assignments work?

PythonPython 3.x

Python Problem Overview


A quote from something:

>>> x = y = somefunction()

is the same as

>>> y = somefunction()
>>> x = y

Question: Is

x = y = somefunction()

the same as

x = somefunction()
y = somefunction()

?

Based on my understanding, they should be same because somefunction can only return exactly one value.

Python Solutions


Solution 1 - Python

Left first

x = y = some_function()

is equivalent to

temp = some_function()
x = temp
y = temp

Note the order. The leftmost target is assigned first. (A similar expression in C may assign in the opposite order.) From the docs on Python assignment:

> ...assigns the single resulting object to each of the target lists, from left to right.

Disassembly shows this:

>>> def chained_assignment():
...     x = y = some_function()
...
>>> import dis
>>> dis.dis(chained_assignment)
  2           0 LOAD_GLOBAL              0 (some_function)
              3 CALL_FUNCTION            0
              6 DUP_TOP
              7 STORE_FAST               0 (x)
             10 STORE_FAST               1 (y)
             13 LOAD_CONST               0 (None)
             16 RETURN_VALUE

CAUTION: the same object is always assigned to each target. So as @Wilduck and @andronikus point out, you probably never want this:

x = y = []   # Wrong.

In the above case x and y refer to the same list. Because lists are mutable, appending to x would seem to affect y.

x = []   # Right.
y = []

Now you have two names referring to two distinct empty lists.

Solution 2 - Python

They will not necessarily work the same if somefunction returns a mutable value. Consider:

>>> def somefunction():
... 	return []
... 
>>> x = y = somefunction()
>>> x.append(4)
>>> x
[4]
>>> y
[4]
>>> x = somefunction(); y = somefunction()
>>> x.append(3)
>>> x
[3]
>>> y
[]

Solution 3 - Python

What if somefunction() returns different values each time it is called?

import random

x = random.random()
y = random.random()

Solution 4 - Python

It would result in the same only if the function has no side-effects and returns a singleton in a deterministic manner (given its inputs).

E.g.:

def is_computer_on():
    return True

x = y = is_computer_on()

or

def get_that_constant():
    return some_immutable_global_constant

Note that the result would be the same, but the process to achieve the result would not:

def slow_is_computer_on():
    sleep(10)
    return True

The content of x and y variables would be the same, but the instruction x = y = slow_is_computer_on() would last 10 seconds, and its counterpart x = slow_is_computer_on() ; y = slow_is_computer_on() would last 20 seconds.

It would be almost the same if the function has no side-effects and returns an immutable in a deterministic manner (given its inputs).

E.g.:

def count_three(i):
    return (i+1, i+2, i+3)

x = y = count_three(42)

Note that the same catches explained in previous section applies.

Why I say almost? Because of this:

x = y = count_three(42)
x is y  # <- is True

x = count_three(42)
y = count_three(42)
x is y  # <- is False

Ok, using is is something strange, but this illustrates that the return is not the same. This is important for the mutable case:

It is dangerous and may lead to bugs if the function returns a mutable

This has also been answered in this question. For the sake of completeness, I replay the argument:

def mutable_count_three(i):
    return [i+1, i+2, i+3]

x = y = mutable_count_three(i)

Because in that scenario x and y are the same object, doing an operation like x.append(42) whould mean that both x and y hold a reference to a list which now has 4 elements.

It would not be the same if the function has side-effects

Considering a print a side-effect (which I find valid, but other examples may be used instead):

def is_computer_on_with_side_effect():
    print "Hello world, I have been called!"
    return True

x = y = is_computer_on_with_side_effect()  # One print

# The following are *two* prints:
x = is_computer_on_with_side_effect()
y = is_computer_on_with_side_effect()

Instead of a print, it may be a more complex or more subtle side-effect, but the fact remains: the method is called once or twice and that may lead to different behaviour.

It would not be the same if the function is non-deterministic given its inputs

Maybe a simple random method:

def throw_dice():
    # This is a 2d6 throw:
    return random.randint(1,6) + random.randint(1,6)

x = y = throw_dice()  # x and y will have the same value

# The following may lead to different values:
x = throw_dice()
y = throw_dice()

But, things related to clock, global counters, system stuff, etc. is sensible to being non-deterministic given the input, and in those cases the value of x and y may diverge.

Solution 5 - Python

As already stated by Bob Stein the order of assignment is important; look at the very interesting following case:

L = L[1] = [42, None]

Now, what does contain L? You must understand that a single object being initially [42, None] which is assigned to L; finally, something like L[1] = L is performed. You thus have some cyclic infinite "list" created (the word "list" being here more similar to some CONS in Lisp with a scalar 42 being the CAR and the list itself being the CDR).

Just type:

>>> L
[42, [...]]

then have some fun by typing L[1], then L[1][1], then L[1][1][1] until you reach the end...

Conclusion

This example is more complicated to understand than other ones in other answers, but on the other hand, you can see much quicker that

L = L[1] = [42, None]

is not the same as

L[1] = L = [42, None]

because the second one will raise an exception if L is not previously defined while the first one will always work.

Solution 6 - Python

In

x = somefunction()
y = somefunction()

somefunction will be called twice instead of once.

Even if it returns the same result each time, this will be a noticeable if it takes a minute to return a result! Or if it has a side effect e.g. asking the user for his password.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionq0987View Question on Stackoverflow
Solution 1 - PythonBob SteinView Answer on Stackoverflow
Solution 2 - PythonWilduckView Answer on Stackoverflow
Solution 3 - PythonGreg HewgillView Answer on Stackoverflow
Solution 4 - PythonMariusSiuramView Answer on Stackoverflow
Solution 5 - PythonThomas BaruchelView Answer on Stackoverflow
Solution 6 - PythonRob CliffeView Answer on Stackoverflow