Почему заданный объект хранится как frozenset, а объект списка - как кортеж? - PullRequest
3 голосов
/ 15 марта 2020

Я видел сообщение в блоге, где упоминается "Используйте func.__code__.co_consts, чтобы проверить все константы, определенные в функции" .

def func():
    return 1 in {1,2,3}
func.__code__.co_consts
(None, 1, <b>frozenset</b>({1, 2, 3}))

Почему он возвращает frozenset ?

def func():
    return 1 in [1,2,3]
func.__code__.co_consts
(None, 1, <b>(1,2,3)</b>)

Почему он возвращает tuple вместо списка? Каждый объект, возвращаемый из __code__.co_consts, является неизменным . Почему изменяемые константы неизменны ? Почему первый элемент возвращаемого кортежа всегда None?

Ответы [ 2 ]

3 голосов
/ 15 марта 2020

Это результат Python Оптимизатора глазка

В разделе «Оптимизации» указано:

BUILD_LIST + COMPARE_OP(in/not in): convert list to tuple
BUILD_SET + COMPARE_OP(in/not in): convert set to frozenset 

См. здесь для получения дополнительной информации:

"Python использует оптимизацию глазка вашего кода путем предварительного вычисления константных выражений или преобразования определенных структур данных"

особенно часть о " Членских тестах":

" Что Python для тестов членства состоит в том, чтобы преобразовать изменяемые структуры данных в их неизменяемую версию. Списки преобразуются в кортежи и наборы в frozensets. "

1 голос
/ 15 марта 2020

Все объекты в co_consts являются константами, т.е. они неизменны. Вы не должны иметь возможность, например, добавлять в список, который выглядит как литерал в исходном коде, и, таким образом, изменять поведение функции.

Компилятор обычно представляет литералы списка, перечисляя все отдельные константы, появляющиеся в список:

>>> def f():
...     a = [1, 2, 3]
...     return 1 in a
... 
>>> f.__code__.co_consts
(None, 1, 2, 3)

Глядя на байт-код этой функции, мы видим, что функция строит список во время выполнения каждый раз, когда функция выполняется:

>>> dis.dis(f)
  2           0 LOAD_CONST               1 (1)
              2 LOAD_CONST               2 (2)
              4 LOAD_CONST               3 (3)
              6 BUILD_LIST               3
              8 STORE_FAST               0 (a)

  3          10 LOAD_CONST               1 (1)
             12 LOAD_FAST                0 (a)
             14 COMPARE_OP               6 (in)
             16 RETURN_VALUE

Создание В общем случае требуется новый список, поскольку функция может изменять или возвращать список, определенный литералом, и в этом случае ей нужно работать с новым объектом списка каждый раз, когда выполняется функция.

В других контекстах, однако создание нового объекта списка расточительно. По этой причине оптимизатор глазков Python может заменить список кортежем или набором frozen_set в определенных ситуациях, когда известно, что он безопасен. Одна из таких ситуаций - когда литерал списка или набора используется только в выражении вида x [not] in <list_literal>. Еще одна такая ситуация - когда литерал списка используется в for l oop.

Оптимизатор глазка очень прост. Это только смотрит на одно выражение за один раз. По этой причине он не может обнаружить, что эта оптимизация будет безопасной в моем определении f выше, что функционально эквивалентно вашему примеру.

...