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
об’єкті, і з цього негайно випливає, що рівні константи потраплять в один і той же слот, що призводить до єдиної константи в кінцевому байт-коді.