Как работают цепные задания?
Цитата из чего-то:
>>> x = y = somefunction()
такой же как
>>> y = somefunction()
>>> x = y
Вопрос: есть
x = y = somefunction()
такой же как
x = somefunction()
y = somefunction()
?
Исходя из моего понимания, они должны быть одинаковыми, потому что somefunction
может вернуть только одно значение.
5 ответов
Они не обязательно будут работать одинаково, если somefunction
возвращает изменяемое значение Рассматривать:
>>> def somefunction():
... return []
...
>>> x = y = somefunction()
>>> x.append(4)
>>> x
[4]
>>> y
[4]
>>> x = somefunction(); y = somefunction()
>>> x.append(3)
>>> x
[3]
>>> y
[]
Ни.
x = y = some_function()
эквивалентно
temp = some_function()
x = temp
y = temp
Обратите внимание на порядок. Крайняя левая цель назначается первой. (Подобное выражение в C может назначаться в обратном порядке.) Из документации по назначению Python:
... назначает один результирующий объект каждому из целевых списков слева направо.
Разборка показывает это:
>>> 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
ВНИМАНИЕ: каждому объекту всегда назначается один и тот же объект. Как указывают @Wilduck и @andronikus, вы, вероятно, никогда не захотите этого:
x = y = [] # Wrong.
В приведенном выше случае x и y относятся к одному и тому же списку. Поскольку списки изменчивы, добавление к x может повлиять на y.
x = [] # Right.
y = []
Теперь у вас есть два имени, относящиеся к двум разным пустым спискам.
Что, если somefunction()
возвращает разные значения каждый раз, когда он вызывается?
import random
x = random.random()
y = random.random()
Это может привести к тому же самому, только если функция не имеет побочных эффектов и возвращает синглтон детерминистическим способом (с учетом ее входных данных).
Например:
def is_computer_on():
return True
x = y = is_computer_on()
или же
def get_that_constant():
return some_immutable_global_constant
Обратите внимание, что результат будет таким же, но процесс для достижения результата не будет:
def slow_is_computer_on():
sleep(10)
return True
Содержимое переменных x и y будет одинаковым, но инструкция x = y = slow_is_computer_on()
будет длиться 10 секунд, и его коллега x = slow_is_computer_on() ; y = slow_is_computer_on()
будет длиться 20 секунд.
Было бы почти то же самое, если бы функция не имела побочных эффектов и возвращала неизменяемость детерминированным образом (с учетом своих входных данных).
Например:
def count_three(i):
return (i+1, i+2, i+3)
x = y = count_three(42)
Обратите внимание, что применяются те же уловы, которые описаны в предыдущем разделе.
Почему я говорю почти? Из-за этого:
x = y = count_three(42)
x is y # <- is True
x = count_three(42)
y = count_three(42)
x is y # <- is False
Хорошо, используя is
это что-то странное, но это показывает, что возвращение не то же самое. Это важно для изменчивого случая:
Это опасно и может привести к ошибкам, если функция возвращает изменяемый
На этот вопрос также был дан ответ в этом вопросе. Ради полноты я переиграю аргумент:
def mutable_count_three(i):
return [i+1, i+2, i+3]
x = y = mutable_count_three(i)
Потому что в этом сценарии x
а также y
один и тот же объект, выполняющий такую операцию, как x.append(42)
что означает, что оба x
а также y
держать ссылку на список, который теперь имеет 4 элемента.
Это не было бы то же самое, если функция имеет побочные эффекты
Учитывая печать побочный эффект (который я считаю действительным, но вместо этого могут быть использованы другие примеры):
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()
Вместо печати это может быть более сложный или более тонкий побочный эффект, но факт остается фактом: метод вызывается один или два раза, и это может привести к другому поведению.
Это не было бы то же самое, если бы функция была недетерминированной, учитывая ее входные данные
Может быть, простой случайный метод:
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()
Но вещи, относящиеся к часам, глобальным счетчикам, системным вещам и т. Д., Имеют смысл быть недетерминированными, учитывая входные данные, и в этих случаях значение x
а также y
может расходиться
Как уже сказал Боб Стейн, важен порядок распределения; посмотрите на очень интересный случай:
L = L[1] = [42, None]
Теперь, что содержит L
? Вы должны понимать, что изначально отдельный объект[42, None]
который закреплен за L
; наконец, что-то вродеL[1] = L
выполняется. Таким образом, у вас есть некоторый циклический бесконечный "список" (слово "список" здесь больше похоже наCONS
в Лиспе со скаляром 42
будучи CAR
и сам список CDR
).
Просто введите:
>>> L
[42, [...]]
затем повеселитесь, набрав L[1]
, тогда L[1][1]
, тогда L[1][1][1]
пока не дойдете до конца...
Вывод
Этот пример сложнее для понимания, чем другие в других ответах, но, с другой стороны, вы можете увидеть гораздо быстрее, что
L = L[1] = [42, None]
не то же самое, что
L[1] = L = [42, None]
потому что второй вызовет исключение, если L
не определен ранее, а первый всегда будет работать.
В
x = somefunction()
y = somefunction()
somefunction
будет вызываться дважды, а не один раз.
Даже если он каждый раз возвращает один и тот же результат, это будет заметно, если для возврата результата потребуется минута! Или, если это имеет побочный эффект, например, спрашивая у пользователя его пароль.