45
votes

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. - agf

6 Answers

46
votes

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

Neither.

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.

15
votes

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

import random

x = random.random()
y = random.random()
6
votes

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.

4
votes

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.

3
votes

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.