После погружения в исходный код Python я обнаружил, что он поддерживает массив PyInt_Object
s в диапазоне от int(-5)
до int(256)
(@ src / Objects / intobject.c)
Небольшой эксперимент это доказывает:
>>> a = 1
>>> b = 1
>>> a is b
True
>>> a = 257
>>> b = 257
>>> a is b
False
Но если я запущу этот код вместе в файле py (или соединю их с точкой с запятой), результат будет другим:
>>> a = 257; b = 257; a is b
True
Мне любопытно, почему они все еще один и тот же объект, поэтому я углубился в синтаксическое дерево и компилятор, я придумал иерархию вызовов, перечисленную ниже:
PyRun_FileExFlags()
mod = PyParser_ASTFromFile()
node *n = PyParser_ParseFileFlagsEx() //source to cst
parsetoke()
ps = PyParser_New()
for (;;)
PyTokenizer_Get()
PyParser_AddToken(ps, ...)
mod = PyAST_FromNode(n, ...) //cst to ast
run_mod(mod, ...)
co = PyAST_Compile(mod, ...) //ast to CFG
PyFuture_FromAST()
PySymtable_Build()
co = compiler_mod()
PyEval_EvalCode(co, ...)
PyEval_EvalCodeEx()
Затем я добавил отладочный код PyInt_FromLong
до и после PyAST_FromNode
и выполнил test.py:
a = 257
b = 257
print "id(a) = %d, id(b) = %d" % (id(a), id(b))
вывод выглядит так:
DEBUG: before PyAST_FromNode
name = a
ival = 257, id = 176046536
name = b
ival = 257, id = 176046752
name = a
name = b
DEBUG: after PyAST_FromNode
run_mod
PyAST_Compile ok
id(a) = 176046536, id(b) = 176046536
Eval ok
Это означает , что во время , cst
чтобы ast
преобразовать два разных PyInt_Object
s созданы ( на самом деле это выполняется в ast_for_atom()
функции), но позже они объединены.
Я считаю , что это трудно понять источник в PyAST_Compile
и PyEval_EvalCode
, таким образом , я здесь , чтобы попросить о помощи, я буду признателен , если кто - то дает подсказку?
Ответы:
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
объект, и из этого сразу следует, что равные константы попадут в один и тот же слот, в результате чего в конечном байт-коде будет одна константа.источник
False
выполняюa = 5.0; b = 5.0; print (a is b)
как py2, так и py3 на win7True
(только перепроверены сейчас). Оптимизация ненадежна, поскольку это всего лишь деталь реализации, так что это не отменяет того момента, который я хотел высказать в своем ответе. Такжеcompile('a=5.0;b=5.0', '<stdin>', 'exec')).co_consts
показывает, что есть только5.0
константа (в python3.3 на linux).