Python кэширует целые числа в диапазоне [-5, 256]
, поэтому ожидается, что целые числа в этом диапазоне также идентичны.
Вы видите, что компилятор Python оптимизирует идентичные литералы, когда они являются частью одного и того же текста.
При вводе в оболочке Python каждая строка представляет собой совершенно другой оператор, анализируемый в другой момент, таким образом:
>>> a = 257
>>> b = 257
>>> a is b
False
Но если вы поместите тот же код в файл:
$ echo 'a = 257
> b = 257
> print a is b' > testing.py
$ python testing.py
True
Это происходит всякий раз, когда у парсера есть возможность проанализировать, где используются литералы, например, при определении функции в интерактивном интерпретаторе:
>>> def test():
... a = 257
... b = 257
... print a is b
...
>>> dis.dis(test)
2 0 LOAD_CONST 1 (257)
3 STORE_FAST 0 (a)
3 6 LOAD_CONST 1 (257)
9 STORE_FAST 1 (b)
4 12 LOAD_FAST 0 (a)
15 LOAD_FAST 1 (b)
18 COMPARE_OP 8 (is)
21 PRINT_ITEM
22 PRINT_NEWLINE
23 LOAD_CONST 0 (None)
26 RETURN_VALUE
>>> test()
True
>>> test.func_code.co_consts
(None, 257)
Обратите внимание, как скомпилированный код содержит единственную константу для 257
.
В заключение, компилятор байт-кода Python не может выполнять массовую оптимизацию (например, языки со статической типизацией), но он делает больше, чем вы думаете. Одна из этих вещей - анализировать использование литералов и избегать их дублирования.
Обратите внимание, что это не связано с кешем, потому что это работает также для чисел с плавающей запятой, у которых нет кеша:
>>> a = 5.0
>>> b = 5.0
>>> a is b
False
>>> a = 5.0; b = 5.0
>>> a is b
True
Для более сложных литералов, таких как кортежи, это «не работает»:
>>> a = (1,2)
>>> b = (1,2)
>>> a is b
False
>>> a = (1,2); b = (1,2)
>>> a is b
False
Но литералы внутри кортежа являются общими:
>>> a = (257, 258)
>>> b = (257, 258)
>>> a[0] is b[0]
False
>>> a[1] is b[1]
False
>>> a = (257, 258); b = (257, 258)
>>> a[0] is b[0]
True
>>> a[1] is b[1]
True
(Обратите внимание, что сворачивание констант и оптимизатор глазка могут изменять поведение даже между версиями исправления ошибок, поэтому какие из примеров возвращаются True
или False
являются произвольными и будут изменены в будущем).
Что касается того, почему вы видите, что PyInt_Object
созданы два , я предполагаю, что это сделано, чтобы избежать буквального сравнения. например, число 257
может быть выражено несколькими литералами:
>>> 257
257
>>> 0x101
257
>>> 0b100000001
257
>>> 0o401
257
У парсера есть два варианта:
- Преобразуйте литералы в некоторую общую основу перед созданием целого числа и посмотрите, эквивалентны ли литералы. затем создайте один целочисленный объект.
- Создайте целочисленные объекты и посмотрите, равны ли они. Если да, оставьте только одно значение и присвойте его всем литералам, в противном случае у вас уже есть целые числа для назначения.
Вероятно, синтаксический анализатор Python использует второй подход, который позволяет избежать переписывания кода преобразования, а также его легче расширить (например, он также работает с числами с плавающей запятой).
При чтении Python/ast.c
файла функция, которая анализирует все числа parsenumber
, вызывает, PyOS_strtoul
чтобы получить целочисленное значение (для целых чисел) и в конечном итоге вызывает PyLong_FromString
:
x = (long) PyOS_strtoul((char *)s, (char **)&end, 0);
if (x < 0 && errno == 0) {
return PyLong_FromString((char *)s,
(char **)0,
0);
}
Как видите, парсер не проверяет, нашел ли он уже целое число с заданным значением, и поэтому это объясняет, почему вы видите, что создаются два объекта int, и это также означает, что мое предположение было правильным: синтаксический анализатор сначала создает константы и только потом оптимизирует байт-код для использования того же объекта для равных констант.
Код, выполняющий эту проверку, должен находиться где-то в Python/compile.c
илиPython/peephole.c
, поскольку это файлы, преобразующие AST в байт-код.
В частности, compiler_add_o
функция кажется той, которая это делает. Этот комментарий есть в compiler_lambda
:
/* Make None the first constant, so the lambda can't have a
docstring. */
if (compiler_add_o(c, c->u->u_consts, Py_None) < 0)
return 0;
Таким образом, похоже, что compiler_add_o
используется для вставки констант для функций / лямбда-выражений и т. Д. compiler_add_o
Функция сохраняет константы в dict
объект, и из этого сразу следует, что равные константы попадут в один и тот же слот, в результате чего в конечном байт-коде будет одна константа.