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

Что происходит с этим изменением режима распаковки с Python2 на Python3

Вчера я столкнулся с этой странной разборкой между Python 2 и Python 3 и, похоже, не нашел никаких объяснений после быстрого поиска Google.

Python 2.7.8

a = 257
b = 257
a is b # False

a, b = 257, 257
a is b # False

Python 3.4.2

a = 257
b = 257
a is b # False

a, b = 257, 257
a is b # True

Я знаю, что это, вероятно, не влияет на правильность программы, но это немного меня беспокоит. Может ли кто-нибудь дать представление о разнице в распаковке?

4b9b3361

Ответ 1

Это поведение, по крайней мере, частично связано с тем, как интерпретатор выполняет постоянную фальцовку и как REPL выполняет код.

Во-первых, помните, что CPython сначала компилирует код (в AST, а затем в байт-код). Затем он оценивает байткод. Во время компиляции script ищет объекты, которые неизменяемы и кэшируют их. Это также дедуплицирует их. Поэтому, если он видит

a = 257
b = 257

он сохранит a и b в отношении одного и того же объекта:

import dis

def f():
    a = 257
    b = 257

dis.dis(f)
#>>>   4           0 LOAD_CONST               1 (257)
#>>>               3 STORE_FAST               0 (a)
#>>>
#>>>   5           6 LOAD_CONST               1 (257)
#>>>               9 STORE_FAST               1 (b)
#>>>              12 LOAD_CONST               0 (None)
#>>>              15 RETURN_VALUE

Обратите внимание на LOAD_CONST 1. 1 - это индекс в co_consts:

f.__code__.co_consts
#>>> (None, 257)

Таким образом, оба загружают одинаковые 257. Почему это не происходит:

$ python2
Python 2.7.8 (default, Sep 24 2014, 18:26:21) 
>>> a = 257
>>> b = 257
>>> a is b
False

$ python3
Python 3.4.2 (default, Oct  8 2014, 13:44:52) 
>>> a = 257
>>> b = 257
>>> a is b
False

?

Каждая строка в этом случае является отдельной единицей компиляции, а дедупликация не может произойти через них. Он работает аналогично

compile a = 257
run     a = 257
compile b = 257
run     b = 257
compile a is b
run     a is b

Таким образом, эти объекты кода будут иметь уникальные постоянные кеши. Это означает, что если мы удалим разрыв строки, is вернет True:

>>> a = 257; b = 257
>>> a is b
True

Действительно, это верно для обеих версий Python. На самом деле, именно поэтому

>>> a, b = 257, 257
>>> a is b
True

возвращает True; это не из-за какого-либо атрибута распаковки; Oни просто поместите его в ту же единицу компиляции.

Это возвращает False для версий, которые не складываются правильно; фильмографические ссылки на Ideone, которые показывают, что это не соответствует требованиям 2.7.3 и 3.2.3. В этих версиях создаваемые кортежи не делят свои элементы с другими константами:

import dis

def f():
    a, b = 257, 257
    print(a is b)

print(f.__code__.co_consts)
#>>> (None, 257, (257, 257))

n = f.__code__.co_consts[1]
n1 = f.__code__.co_consts[2][0]
n2 = f.__code__.co_consts[2][1]

print(id(n), id(n1), id(n2))
#>>> (148384292, 148384304, 148384496)

Опять же, это не связано с изменением того, как объекты распакованы; это только изменение того, как объекты хранятся в co_consts.

Ответ 2

Я думаю, что это происходит случайно, поскольку я не могу воспроизвести поведение с Python 3.2.

Существует проблема http://bugs.python.org/issue11244, которая вводит CONST_STACK для исправления проблем с постоянными кортежами с отрицательными числами, которые не оптимизируются (смотрите патчи против peephole.c, которые содержат оптимизаторы Python).

Это, похоже, также привело к данному поведению. Все еще смотря на это:)