Estou atrasado, mas você quer alguma fonte com a sua resposta? Vou tentar escrever isso de maneira introdutória para que mais pessoas possam acompanhar.
Uma coisa boa do CPython é que você pode realmente ver a fonte disso. Vou usar links para a versão 3.5 , mas encontrando o 2.x correspondente é trivial.
No CPython, a função C-API que lida com a criação de um novo int
objeto é PyLong_FromLong(long v)
. A descrição para esta função é:
A implementação atual mantém uma matriz de objetos inteiros para todos os números inteiros entre -5 e 256. Quando você cria um int nesse intervalo, na verdade, apenas recebe uma referência ao objeto existente . Portanto, deve ser possível alterar o valor de 1. Suspeito que o comportamento do Python neste caso seja indefinido. :-)
(Meus itálicos)
Não sei sobre você, mas eu vejo isso e penso: vamos encontrar essa matriz!
Se você não mexeu no código C implementando o CPython, deveria ; tudo é bem organizado e legível. Para o nosso caso, precisamos procurar no Objects
subdiretório da árvore de diretórios do código fonte principal .
PyLong_FromLong
lida com long
objetos, portanto não deve ser difícil deduzir que precisamos espiar por dentro longobject.c
. Depois de olhar para dentro, você pode pensar que as coisas são caóticas; elas são, mas não temam, a função que procuramos é relaxar na linha 230, esperando que a verifiquemos. Como é uma função pequena, o corpo principal (excluindo as declarações) é facilmente colado aqui:
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;
}
Agora, não somos C -master-code-haxxorz, mas também não somos burros, podemos ver isso CHECK_SMALL_INT(ival);
nos observando sedutoramente; podemos entender que isso tem algo a ver com isso. Vamos conferir:
#define CHECK_SMALL_INT(ival) \
do if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) { \
return get_small_int((sdigit)ival); \
} while(0)
Portanto, é uma macro que chama função get_small_int
se o valor ival
satisfizer a condição:
if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS)
Então, o que são NSMALLNEGINTS
e NSMALLPOSINTS
? Macros! Aqui estão elas :
#ifndef NSMALLPOSINTS
#define NSMALLPOSINTS 257
#endif
#ifndef NSMALLNEGINTS
#define NSMALLNEGINTS 5
#endif
Então, nossa condição é if (-5 <= ival && ival < 257)
chamada get_small_int
.
A seguir, veremos get_small_int
toda a sua glória (bem, apenas veremos seu corpo porque é aí que estão as coisas interessantes):
PyObject *v;
assert(-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS);
v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];
Py_INCREF(v);
Ok, declare a PyObject
, afirme que a condição anterior mantém e execute a atribuição:
v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];
small_ints
parece muito com a matriz que estamos procurando, e é! Poderíamos ter lido a maldita documentação e saberíamos o tempo todo! :
/* 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];
Então sim, esse é o nosso cara. Quando você deseja criar um novo int
no intervalo, [NSMALLNEGINTS, NSMALLPOSINTS)
basta obter uma referência a um objeto já existente que foi pré-alocado.
Como a referência se refere ao mesmo objeto, emitindo id()
diretamente ou verificando a identidade comis
ele retornará exatamente a mesma coisa.
Mas, quando eles são alocados?
Durante a inicialização em_PyLong_Init
Python, com prazer, entrará em um loop for, faça isso por você:
for (ival = -NSMALLNEGINTS; ival < NSMALLPOSINTS; ival++, v++) {
Confira a fonte para ler o corpo do loop!
Espero que a minha explicação te fez C coisas claramente agora (trocadilho obviamente intented).
Mas 257 is 257
? E aí?
Isso é realmente mais fácil de explicar, e eu já tentei fazê-lo ; isso se deve ao fato de o Python executar esta declaração interativa como um único bloco:
>>> 257 is 257
Durante a conclusão desta declaração, o CPython verá que você tem dois literais correspondentes e usará a mesma PyLongObject
representação 257
. Você pode ver isso se fizer a compilação e examinar seu conteúdo:
>>> codeObj = compile("257 is 257", "blah!", "exec")
>>> codeObj.co_consts
(257, None)
Quando o CPython faz a operação, agora ele carrega exatamente o mesmo objeto:
>>> import dis
>>> dis.dis(codeObj)
1 0 LOAD_CONST 0 (257) # dis
3 LOAD_CONST 0 (257) # dis again
6 COMPARE_OP 8 (is)
Então is
vai voltar True
.