How do chained assignments work?

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.

  • 6

    You might want to use the python tag instead of python-3.x since it’s more widely followed and your question isn’t specific to Python 3. You also don’t need to reiterate the tag in the title, but it is good to mention your version of Python somewhere.

    – 

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.

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
[]

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

import random

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

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.

Leave a Comment