Python met en cache les entiers dans l'intervalle [-5, 256]
donc les nombres entiers dans cet intervalle sont généralement mais pas toujours identique.
Ce que vous voyez pour 257 est le compilateur Python qui optimise des littéraux identiques lorsqu'ils sont compilés dans le même objet de code.
Lorsque vous tapez dans le shell Python, chaque ligne est une déclaration complètement différente, analysée et compilée séparément, ainsi :
>>> a = 257
>>> b = 257
>>> a is b
False
Mais si vous mettez le même code dans un fichier :
$ echo 'a = 257
> b = 257
> print a is b' > testing.py
$ python testing.py
True
Cela se produit chaque fois que le compilateur a la possibilité d'analyser les littéraux ensemble, par exemple lors de la définition d'une fonction dans l'interpréteur interactif :
>>> def test():
... a = 257
... b = 257
... print a is b
...
>>> dis.dis(test)
2 0 LOAD_CONST 1 (257)
3 STORE_FAST 0 (a)
3 6 LOAD_CONST 1 (257)
9 STORE_FAST 1 (b)
4 12 LOAD_FAST 0 (a)
15 LOAD_FAST 1 (b)
18 COMPARE_OP 8 (is)
21 PRINT_ITEM
22 PRINT_NEWLINE
23 LOAD_CONST 0 (None)
26 RETURN_VALUE
>>> test()
True
>>> test.func_code.co_consts
(None, 257)
Notez comment le code compilé contient une seule constante pour l'élément 257
.
En conclusion, le compilateur de bytecode Python n'est pas capable d'effectuer des optimisations massives (comme les langages typés statiquement), mais il fait plus que vous ne le pensez. L'une de ces choses est d'analyser l'utilisation des littéraux et d'éviter de les dupliquer.
Notez que cela n'a rien à voir avec le cache, car cela fonctionne également pour les flottants, qui n'ont pas de cache :
>>> a = 5.0
>>> b = 5.0
>>> a is b
False
>>> a = 5.0; b = 5.0
>>> a is b
True
Pour les littéraux plus complexes, comme les tuples, cela "ne fonctionne pas" :
>>> a = (1,2)
>>> b = (1,2)
>>> a is b
False
>>> a = (1,2); b = (1,2)
>>> a is b
False
Mais les littéraux à l'intérieur du tuple sont partagés :
>>> a = (257, 258)
>>> b = (257, 258)
>>> a[0] is b[0]
False
>>> a[1] is b[1]
False
>>> a = (257, 258); b = (257, 258)
>>> a[0] is b[0]
True
>>> a[1] is b[1]
True
(Notez que le pliage constant et l'optimiseur peephole peuvent changer de comportement même entre les versions de correction de bogues, donc les exemples qui retournent True
ou False
est fondamentalement arbitraire et changera à l'avenir).
En ce qui concerne la raison pour laquelle vous voyez que deux PyInt_Object
sont créés, je devinez que cela est fait pour éviter la comparaison littérale. par exemple, le nombre 257
peut être exprimée par plusieurs littéraux :
>>> 257
257
>>> 0x101
257
>>> 0b100000001
257
>>> 0o401
257
L'analyseur syntaxique a deux choix :
- Convertissez les littéraux en une base commune avant de créer le nombre entier, et voyez si les littéraux sont équivalents. Créez ensuite un objet entier unique.
- Créez les objets integer et voyez s'ils sont égaux. Si oui, gardez une seule valeur et assignez-la à tous les littéraux, sinon, vous avez déjà les entiers à assigner.
Le parseur Python utilise probablement la deuxième approche, qui évite de réécrire le code de conversion et qui est également plus facile à étendre (par exemple, il fonctionne également avec les flottants).
Lire le Python/ast.c
la fonction qui analyse tous les nombres est parsenumber
qui appelle PyOS_strtoul
pour obtenir la valeur entière (pour les intgers) et appelle éventuellement PyLong_FromString
:
x = (long) PyOS_strtoul((char *)s, (char **)&end, 0);
if (x < 0 && errno == 0) {
return PyLong_FromString((char *)s,
(char **)0,
0);
}
Comme vous pouvez le voir ici, le parseur fait no vérifie s'il a déjà trouvé un entier avec la valeur donnée et cela explique donc pourquoi vous voyez que deux objets int sont créés, et cela signifie également que ma supposition était correcte : l'analyseur syntaxique crée d'abord les constantes et seulement après optimise le bytecode pour utiliser le même objet pour des constantes égales.
Le code qui effectue cette vérification doit se trouver quelque part dans le fichier Python/compile.c
ou Python/peephole.c
car ce sont les fichiers qui transforment l'AST en bytecode.
En particulier, le compiler_add_o
semble être celle qui le fait. Il y a ce commentaire dans compiler_lambda
:
/* Make None the first constant, so the lambda can't have a
docstring. */
if (compiler_add_o(c, c->u->u_consts, Py_None) < 0)
return 0;
Donc il semble que compiler_add_o
est utilisé pour insérer des constantes pour les fonctions/lambdas etc. Le site compiler_add_o
stocke les constantes dans un fichier dict
et il en découle immédiatement que des constantes égales tomberont dans le même slot, ce qui donne une constante unique dans le bytecode final.