Я опоздал, но вам нужен источник с вашим ответом? *
Хорошо, что в 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
. Заглянув внутрь, вы можете подумать, что все хаотично; они, но не бойтесь, функция, которую мы ищем, охлаждает по line 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
master-code-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++) {
// Look me up!
}
Я надеюсь, что мое объяснение сделало вас C
(каламбур явно намеренным) теперь ясно.
Но 257 - это 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
.
* - Я постараюсь сформулировать это более вводным образом, чтобы большинство могло следовать за ним.