How do chained assignments work?
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.
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.
What if somefunction()
returns different values each time it is called?
import random
x = random.random()
y = random.random()
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
[]