Я опоздал, но вы хотите какой-нибудь источник с вашим ответом? Я попытаюсь сказать это во вступительной манере, чтобы больше людей могли следовать за ними.
Хорошая вещь о CPython в том, что вы можете увидеть источник этого. Я собираюсь использовать ссылки для версии 3.5 , но найти соответствующие 2.x тривиально.
В CPython функция C-API, которая обрабатывает создание нового int
объекта PyLong_FromLong(long v)
. Описание этой функции:
Текущая реализация хранит массив целочисленных объектов для всех целых чисел от -5 до 256, когда вы создаете int в этом диапазоне, вы на самом деле просто получаете ссылку на существующий объект . Так что должно быть возможно изменить значение 1. Я подозреваю, что поведение Python в этом случае не определено. :-)
(Мой курсив)
Не знаю как вы, но я вижу это и думаю: давайте найдем этот массив!
Если вы не возились с кодом C, реализующим 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;
}
Так вот, мы не мастер-код С-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);
Хорошо, объявляем a 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++) {
Проверьте источник, чтобы прочитать тело цикла!
Я надеюсь, что мое объяснение сделало вас ясными с вещами теперь (игра слов явно была намерена).
Но 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
.