How do chained assignments work?

Question:

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.

Asked By: q0987

||

Answers:

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

import random

x = random.random()
y = random.random()
Answered By: Greg Hewgill

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
[]
Answered By: Wilduck

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.

Answered By: Rob Cliffe

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.

Answered By: Bob Stein

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.

Answered By: MariusSiuram

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.

Answered By: Thomas Baruchel
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.