Подтвердить что ты не робот

В чем разница между деструктивным назначением и "нормальным" назначением?

Я играл в python 2.7.6 REPL и сталкивался с этим поведением.

>>> x = -10
>>> y = -10
>>> x is y
False
>>> x, y = [-10, -10]
>>> x is y
True

Кажется, что деструктурированное присваивание возвращает ту же ссылку для эквивалентных значений. Почему это?

4b9b3361

Ответ 1

Я ничего не знаю о Python, но мне было любопытно.

Во-первых, это происходит при назначении массива:

x = [-10,-10]
x[0] is x[1]  # True

Это также происходит со строками, которые неизменяемы.

x = ['foo', 'foo']
x[0] is x[1]  # True

Разборка первой функции:

         0 LOAD_CONST               1 (-10)
         3 LOAD_CONST               1 (-10)
         6 BUILD_LIST               2
         9 STORE_FAST               0 (x)

Оператор LOAD_CONST (consti) создает постоянный co_consts[consti] в стек. Но оба ops имеют consti=1, поэтому один и тот же объект дважды помещается в стек. Если числа в массиве были разными, он бы разобрался с этим:

         0 LOAD_CONST               1 (-10)
         3 LOAD_CONST               2 (-20)
         6 BUILD_LIST               2
         9 STORE_FAST               0 (x)

Здесь скопированы константы индексов 1 и 2.

co_consts является кортежем констант, используемых Python script. Очевидно, что литералы с одинаковым значением сохраняются только один раз.

Что касается того, почему работает "нормальное" назначение, вы используете REPL, поэтому я предполагаю, что каждая строка компилируется отдельно. Если вы положите

x = -10
y = -10
print(x is y)

в тест script, вы получите True. Таким образом, нормальное назначение и деструктивное назначение работают одинаково в этом отношении:)

Ответ 2

Что происходит, так это то, что интерактивный интерпретатор Python компилирует каждый оператор отдельно. Компиляция не только создает байт-код, но также создает константы для любого встроенного неизменяемого типа, включая целые числа. Эти константы хранятся вместе с объектом кода как атрибут co_consts.

Ваш x = -10 скомпилирован отдельно от назначения y = -10, и вы получите полностью отдельные структуры co_consts. С другой стороны, ваше итерационное назначение x, y = [-10, -10] - это единый оператор присваивания, который передается компилятору сразу, поэтому компилятор может повторно использовать константы.

Вы можете поместить простые операторы (например, назначения) в одну строку с точкой с запятой между ними, и в этот момент в Python 2.7 вы снова получите тот же объект -10:

>>> x = -10; y = -10
>>> x is y
True

Здесь мы снова скомпилировали один оператор, поэтому компилятор может решить, что ему нужен только один объект для представления значения -10:

>>> compile('x = -10; y = -10', '', 'single').co_consts
(-10, None)

'single' - это режим компиляции, который использует интерактивный интерпретатор. Скомпилированный байт-код загружает значение -10 из этих констант.

Вы получите то же самое, если поместите все в функцию, скомпилированную как единый составной оператор:

>>> def foo():
...     x = -10
...     y = -10
...     return x is y
...
>>> foo()
True
>>> foo.__code__.co_consts
(None, -10)

Модули также скомпилированы за один проход, поэтому глобальные модули в модуле могут совместно использовать константы.

Все это деталь реализации. Вы должны никогда, когда-либо рассчитывать на это.

Например, в Python 3.6 унарный минус-оператор обрабатывается отдельно (вместо -10 рассматривается как единственный целочисленный литерал), а значение -10 достигается после постоянной сгибания во время оптимизации глазок. Это позволяет получить два отдельных значения -10:

>>> import sys
>>> sys.version_info
sys.version_info(major=3, minor=6, micro=3, releaselevel='final', serial=0)
>>> compile('x = -10; y = -10', '', 'single').co_consts
(10, None, -10, -10)

Другие реализации Python (PyPy, Jython, IronPython и т.д.) могут свободно обрабатывать константы по-разному.