В чем разница между деструктивным назначением и "нормальным" назначением?
Я играл в python 2.7.6 REPL и сталкивался с этим поведением.
>>> x = -10
>>> y = -10
>>> x is y
False
>>> x, y = [-10, -10]
>>> x is y
True
Кажется, что деструктурированное присваивание возвращает ту же ссылку для эквивалентных значений. Почему это?
Ответы
Ответ 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 и т.д.) могут свободно обрабатывать константы по-разному.