Я спізнююсь, але ви хочете отримати джерело зі своєю відповіддю? Я спробую сказати це у вступному порядку, щоб більше людей могли слідувати далі.
Хороша річ у CPython - це те, що ви можете насправді бачити джерело для цього. Я збираюся використовувати посилання для версії 3.5 , але знайти відповідні 2.x - тривіально.
У CPython є функцією C-API, яка обробляє створення нового int
об'єкта PyLong_FromLong(long v)
. Опис цієї функції:
Поточна реалізація зберігає масив цілих об'єктів для всіх цілих чисел між -5 і 256, коли ви створюєте int в цьому діапазоні, ви фактично просто отримуєте посилання на існуючий об'єкт . Тож має бути можливість змінити значення 1. Я підозрюю, що поведінка Python у цьому випадку не визначена. :-)
(Мій курсив)
Не знаю про вас, але я бачу це і думаю: Давайте знайдемо цей масив!
Якщо ви не зіткнулися з кодом С, що реалізує CPython, вам слід ; все досить організовано і читабельно. Для нашого випадку, ми повинні дивитися в Objects
підкаталозі з основного вихідного коду дерева каталогів .
PyLong_FromLong
має справу з long
предметами, тому не повинно бути важко зробити висновок, що нам потрібно зазирнути всередину longobject.c
. Заглянувши всередину, ви можете подумати, що речі хаотичні; вони є, але не бійтеся, функція, яку ми шукаємо, охолоджує на лінії 230 очікування, коли ми перевіримо її. Це невелика функція, тому основний корпус (крім декларацій) тут легко вставляється:
PyObject *
PyLong_FromLong(long ival)
{
// omitting declarations
CHECK_SMALL_INT(ival);
if (ival < 0) {
/* negate: cant write this as abs_ival = -ival since that
invokes undefined behaviour when ival is LONG_MIN */
abs_ival = 0U-(unsigned long)ival;
sign = -1;
}
else {
abs_ival = (unsigned long)ival;
}
/* Fast path for single-digit ints */
if (!(abs_ival >> PyLong_SHIFT)) {
v = _PyLong_New(1);
if (v) {
Py_SIZE(v) = sign;
v->ob_digit[0] = Py_SAFE_DOWNCAST(
abs_ival, unsigned long, digit);
}
return (PyObject*)v;
}
Тепер ми не мастер-код C -haxxorz, але ми також не тупі, ми можемо побачити, що CHECK_SMALL_INT(ival);
заглядають на нас усіх спокусливо; ми можемо зрозуміти, що це має щось спільне з цим. Давайте перевіримо:
#define CHECK_SMALL_INT(ival) \
do if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) { \
return get_small_int((sdigit)ival); \
} while(0)
Отже, це макрос, який викликає функцію, get_small_int
якщо значення ival
задовольняє умові:
if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS)
То що таке NSMALLNEGINTS
і NSMALLPOSINTS
? Макроси! Ось вони :
#ifndef NSMALLPOSINTS
#define NSMALLPOSINTS 257
#endif
#ifndef NSMALLNEGINTS
#define NSMALLNEGINTS 5
#endif
Тож наша умова - if (-5 <= ival && ival < 257)
дзвінок get_small_int
.
Далі давайте подивимось get_small_int
у всій красі (ну ми просто подивимось на його тіло, бо саме тут цікаві речі):
PyObject *v;
assert(-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS);
v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];
Py_INCREF(v);
Гаразд, оголосити "А" PyObject
, запевнити, що попередня умова виконує та виконує завдання:
v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];
small_ints
дуже схоже на той масив, який ми шукали, і це! Ми могли просто прочитати прокляту документацію, і ми б все це знали! :
/* Small integers are preallocated in this array so that they
can be shared.
The integers that are preallocated are those in the range
-NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive).
*/
static PyLongObject small_ints[NSMALLNEGINTS + NSMALLPOSINTS];
Так що, це наш хлопець. Коли ви хочете створити новий int
у діапазоні, [NSMALLNEGINTS, NSMALLPOSINTS)
ви просто отримаєте посилання на вже наявний об'єкт, який був попередньо виділений.
Оскільки посилання стосується одного і того ж об'єкта, видача id()
безпосередньо або перевірка на тотожність is
на ньому поверне точно те саме.
Але, коли вони виділяються ??
Під час ініціалізації в_PyLong_Init
Python із задоволенням ввійдете в цикл for зробіть це для вас:
for (ival = -NSMALLNEGINTS; ival < NSMALLPOSINTS; ival++, v++) {
Перевірте джерело, щоб прочитати тіло циклу!
Я сподіваюсь, що моє пояснення зрозуміло, що ви зараз зрозуміли C ( речі, очевидно, навмисні)
Але 257 is 257
,? Як справи?
Це насправді простіше пояснити, і я вже намагався це зробити ; це пов'язано з тим, що Python буде виконувати це інтерактивне твердження як єдиний блок:
>>> 257 is 257
Під час компіляції цього твердження CPython побачить, що у вас є дві відповідні літерали і буде використовувати одне і те ж PyLongObject
представлення 257
. Ви можете це побачити, якщо скласти компіляцію самостійно та вивчити її вміст:
>>> codeObj = compile("257 is 257", "blah!", "exec")
>>> codeObj.co_consts
(257, None)
Коли CPython виконує операцію, тепер просто збирається вкласти той самий об'єкт:
>>> import dis
>>> dis.dis(codeObj)
1 0 LOAD_CONST 0 (257) # dis
3 LOAD_CONST 0 (257) # dis again
6 COMPARE_OP 8 (is)
Так is
повернемось True
.