Как работают цепные задания?

Цитата из чего-то:

>>> 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 будет вызываться дважды, а не один раз.

Даже если он каждый раз возвращает один и тот же результат, это будет заметно, если для возврата результата потребуется минута! Или, если это имеет побочный эффект, например, спрашивая у пользователя его пароль.

Другие вопросы по тегам