Je suis en retard mais, vous voulez une source avec votre réponse? Je vais essayer de formuler ceci de manière introductive afin que plus de gens puissent suivre.
Une bonne chose à propos de CPython est que vous pouvez réellement voir la source de cela. Je vais utiliser des liens pour la version 3.5 , mais trouver le 2.x correspondant ceux qui est trivial.
Dans CPython, la fonction C-API qui gère la création d'un nouvel int
objet est PyLong_FromLong(long v)
. La description de cette fonction est:
L'implémentation actuelle conserve un tableau d'objets entiers pour tous les entiers compris entre -5 et 256, lorsque vous créez un int dans cette plage, vous récupérez simplement une référence à l'objet existant . Il devrait donc être possible de changer la valeur de 1. Je soupçonne que le comportement de Python dans ce cas n'est pas défini. :-)
(Mes italiques)
Je ne sais pas pour vous mais je vois cela et je pense: Trouvons ce tableau!
Si vous n'avez pas manipulé le code C implémentant CPython, vous devriez ; tout est assez organisé et lisible. Pour notre cas, nous devons regarder dans le Objects
sous - répertoire de l' arborescence du répertoire du code source principal .
PyLong_FromLong
traite des long
objets, il ne devrait donc pas être difficile de déduire que nous devons jeter un œil à l'intérieur longobject.c
. Après avoir regardé à l'intérieur, vous pourriez penser que les choses sont chaotiques; ils le sont, mais n'ayez crainte, la fonction que nous recherchons est de se détendre à la ligne 230 en attendant que nous le vérifions. C'est une fonction assez petite, donc le corps principal (à l'exclusion des déclarations) est facilement collé ici:
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;
}
Maintenant, nous ne sommes pas un C master-code-haxxorz mais nous ne sommes pas non plus stupides, nous pouvons voir que CHECK_SMALL_INT(ival);
nous jeter un œil séduisant; nous pouvons comprendre que cela a quelque chose à voir avec cela. Regardons ça:
#define CHECK_SMALL_INT(ival) \
do if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) { \
return get_small_int((sdigit)ival); \
} while(0)
C'est donc une macro qui appelle la fonction get_small_int
si la valeur ival
satisfait la condition:
if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS)
Alors, quels sont NSMALLNEGINTS
et NSMALLPOSINTS
? Macros! Les voici :
#ifndef NSMALLPOSINTS
#define NSMALLPOSINTS 257
#endif
#ifndef NSMALLNEGINTS
#define NSMALLNEGINTS 5
#endif
Notre condition est donc l' if (-5 <= ival && ival < 257)
appel get_small_int
.
Ensuite, regardons get_small_int
dans toute sa splendeur (eh bien, nous allons simplement regarder son corps parce que c'est là que les choses intéressantes sont):
PyObject *v;
assert(-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS);
v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];
Py_INCREF(v);
D'accord, déclarez a PyObject
, affirmez que la condition précédente est vérifiée et exécutez l'affectation:
v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];
small_ints
ressemble beaucoup à ce tableau que nous recherchions, et il l'est! Nous aurions pu lire la fichue documentation et nous l'aurions su tout au long! :
/* 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];
Alors ouais, c'est notre gars. Lorsque vous souhaitez créer un nouveau int
dans la plage, [NSMALLNEGINTS, NSMALLPOSINTS)
vous récupérez simplement une référence à un objet déjà existant qui a été préalloué.
Étant donné que la référence se réfère au même objet, délivrer id()
directement ou vérifier l'identité avecis
dessus retournera exactement la même chose.
Mais, quand sont-ils alloués ??
Lors de l'initialisation en_PyLong_Init
Python, il vous fera plaisir d'entrer dans une boucle for, faites-le pour vous:
for (ival = -NSMALLNEGINTS; ival < NSMALLPOSINTS; ival++, v++) {
Consultez la source pour lire le corps de la boucle!
J'espère que mon explication vous a fait des choses C clairement maintenant (jeu de mots évidemment intentionnel).
Mais 257 is 257
,? Quoi de neuf?
C'est en fait plus facile à expliquer, et j'ai déjà essayé de le faire ; c'est dû au fait que Python exécutera cette instruction interactive comme un seul bloc:
>>> 257 is 257
Lors de la compilation de cette instruction, CPython verra que vous avez deux littéraux correspondants et utilisera le même PyLongObject
représentant 257
. Vous pouvez le voir si vous faites vous-même la compilation et examinez son contenu:
>>> codeObj = compile("257 is 257", "blah!", "exec")
>>> codeObj.co_consts
(257, None)
Lorsque CPython effectue l'opération, il va maintenant simplement charger exactement le même objet:
>>> import dis
>>> dis.dis(codeObj)
1 0 LOAD_CONST 0 (257) # dis
3 LOAD_CONST 0 (257) # dis again
6 COMPARE_OP 8 (is)
Ainsi is
reviendra True
.