Pourquoi les éléments suivants se comportent-ils de manière inattendue en Python?
>>> a = 256
>>> b = 256
>>> a is b
True # This is an expected result
>>> a = 257
>>> b = 257
>>> a is b
False # What happened here? Why is this False?
>>> 257 is 257
True # Yet the literal numbers compare properly
J'utilise Python 2.5.2. En essayant différentes versions de Python, il semble que Python 2.3.3 montre le comportement ci-dessus entre 99 et 100.
Sur la base de ce qui précède, je peux émettre l'hypothèse que Python est implémenté en interne de telle sorte que les "petits" entiers sont stockés d'une manière différente des entiers plus grands et que l' is
opérateur peut faire la différence. Pourquoi l'abstraction qui fuit? Quelle est la meilleure façon de comparer deux objets arbitraires pour voir s'ils sont identiques quand je ne sais pas à l'avance s'il s'agit de nombres ou non?
Réponses:
Regarde ça:
Voici ce que j'ai trouvé dans la documentation Python 2, "Plain Integer Objects" (C'est la même chose pour Python 3 ):
la source
En résumé - permettez-moi de souligner: ne pas utiliser
is
pour comparer des entiers.Ce n'est pas un comportement dont vous devriez avoir des attentes.
Utilisez plutôt
==
et!=
pour comparer l'égalité et l'inégalité, respectivement. Par exemple:Explication
Pour le savoir, vous devez connaître les éléments suivants.
D'abord, que fait
is
-on? C'est un opérateur de comparaison. De la documentation :Et donc les éléments suivants sont équivalents.
De la documentation :
Notez que le fait que l'id d'un objet dans CPython (l'implémentation de référence de Python) soit l'emplacement en mémoire est un détail d'implémentation. D'autres implémentations de Python (telles que Jython ou IronPython) pourraient facilement avoir une implémentation différente pour
id
.Alors, à quoi sert le cas d'utilisation
is
? PEP8 décrit :La question
Vous posez et énoncez la question suivante (avec code):
Ce n'est pas un résultat attendu. Pourquoi est-il attendu? Cela signifie seulement que les entiers évalués à
256
référencés par les deuxa
etb
sont la même instance d'entier. Les nombres entiers sont immuables en Python, donc ils ne peuvent pas changer. Cela ne devrait avoir aucun impact sur aucun code. Il ne faut pas s'y attendre. Il s'agit simplement d'un détail d'implémentation.Mais peut-être devrions-nous être heureux qu'il n'y ait pas de nouvelle instance distincte en mémoire chaque fois que nous déclarons une valeur égale à 256.
On dirait que nous avons maintenant deux instances distinctes d'entiers avec la valeur de
257
en mémoire. Étant donné que les entiers sont immuables, cela gaspille de la mémoire. Espérons que nous n'en perdons pas beaucoup. Nous ne le sommes probablement pas. Mais ce comportement n'est pas garanti.Eh bien, cela ressemble à votre implémentation particulière de Python qui essaie d'être intelligente et de ne pas créer d'entiers à valeur redondante à moins que cela ne soit nécessaire. Vous semblez indiquer que vous utilisez l'implémentation référente de Python, qui est CPython. Bon pour CPython.
Il serait peut-être encore mieux que CPython puisse le faire à l'échelle mondiale, s'il le pouvait à moindre coût (car il y aurait un coût dans la recherche), peut-être qu'une autre implémentation pourrait le faire.
Mais en ce qui concerne l'impact sur le code, vous ne devriez pas vous soucier si un entier est une instance particulière d'un entier. Vous ne devriez vous soucier que de la valeur de cette instance, et vous utiliseriez les opérateurs de comparaison normaux pour cela, c'est-à-dire
==
.Qu'est
is
- ce queis
vérifie que lesid
deux objets sont identiques. Dans CPython, l'id
emplacement est en mémoire, mais il peut s'agir d'un autre numéro d'identification unique dans une autre implémentation. Pour reformuler cela avec du code:est le même que
Pourquoi voudrions-nous utiliser
is
alors?Cela peut être une vérification très rapide par rapport à, par exemple, vérifier si deux chaînes très longues sont égales en valeur. Mais puisqu'il s'applique à l'unicité de l'objet, nous avons donc des cas d'utilisation limités pour lui. En fait, nous voulons surtout l'utiliser pour rechercher
None
, qui est un singleton (une instance unique existant à un endroit en mémoire). Nous pourrions créer d'autres singletons s'il est possible de les confondre, ce que nous pourrions vérifieris
, mais ceux-ci sont relativement rares. Voici un exemple (fonctionnera en Python 2 et 3) par exempleQui imprime:
Et donc nous voyons, avec
is
et une sentinelle, nous pouvons faire la différence entre quandbar
est appelé sans argument et quand il est appelé avecNone
. Ce sont les cas d' utilisation primaire pouris
- ne pas l' utiliser pour tester l'égalité des entiers, des chaînes, tuples, ou d' autres choses comme celles - ci.la source
is
- ne l'utilisez pas pour tester l'égalité des entiers, des chaînes, des tuples ou d'autres choses comme celles-ci." Cependant, j'essaie d'intégrer une machine à états simple dans ma classe, et comme les états sont des valeurs opaques dont la seule propriété observable est celle d'être identiques ou différents, il semble tout à fait naturel qu'ils soient comparablesis
. J'ai l'intention d'utiliser des chaînes internes comme états. J'aurais préféré des entiers simples, mais malheureusement Python ne peut pas interner des entiers (0 is 0
c'est un détail d'implémentation).Cela dépend si vous cherchez à voir si 2 choses sont égales ou le même objet.
is
vérifie s'il s'agit du même objet, pas seulement égal. Les petites entrées pointent probablement vers le même emplacement de mémoire pour une économie d'espaceVous devez utiliser
==
pour comparer l'égalité des objets arbitraires. Vous pouvez spécifier le comportement avec les attributs__eq__
et__ne__
.la source
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 estPyLong_FromLong(long v)
. La description de cette fonction est:(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 deslong
objets, il ne devrait donc pas être difficile de déduire que nous devons jeter un œil à l'intérieurlongobject.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: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:C'est donc une macro qui appelle la fonction
get_small_int
si la valeurival
satisfait la condition:Alors, quels sont
NSMALLNEGINTS
etNSMALLPOSINTS
? Macros! Les voici :Notre condition est donc l'
if (-5 <= ival && ival < 257)
appelget_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):D'accord, déclarez a
PyObject
, affirmez que la condition précédente est vérifiée et exécutez l'affectation: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! :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: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:
Lors de la compilation de cette instruction, CPython verra que vous avez deux littéraux correspondants et utilisera le même
PyLongObject
représentant257
. Vous pouvez le voir si vous faites vous-même la compilation et examinez son contenu:Lorsque CPython effectue l'opération, il va maintenant simplement charger exactement le même objet:
Ainsi
is
reviendraTrue
.la source
Comme vous pouvez archiver le fichier source intobject.c , Python met en cache de petits entiers pour plus d'efficacité. Chaque fois que vous créez une référence à un petit entier, vous faites référence au petit entier mis en cache, pas à un nouvel objet. 257 n'est pas un petit entier, il est donc calculé comme un objet différent.
Il vaut mieux l'utiliser
==
à cette fin.la source
Je pense que vos hypothèses sont correctes. Expérience avec
id
(identité de l'objet):Il semble que les nombres
<= 255
soient traités comme des littéraux et tout ce qui précède est traité différemment!la source
Pour les objets à valeur immuable, comme les entiers, les chaînes ou les heures de données, l'identité de l'objet n'est pas particulièrement utile. Il vaut mieux penser à l'égalité. L'identité est essentiellement un détail d'implémentation pour les objets de valeur - puisqu'ils sont immuables, il n'y a pas de différence effective entre avoir plusieurs références au même objet ou plusieurs objets.
la source
Il y a un autre problème qui n'est souligné dans aucune des réponses existantes. Python est autorisé à fusionner deux valeurs immuables, et les petites valeurs int pré-créées ne sont pas le seul moyen pour que cela se produise. Une implémentation Python n'est jamais garantie pour cela, mais ils le font tous pour plus que de petits chiffres.
D'une part, il existe d'autres valeurs pré-créées, telles que les chaînes vides
tuple
,str
etbytes
, et certaines chaînes courtes (dans CPython 3.6, il s'agit des 256 chaînes Latin-1 à un seul caractère). Par exemple:Mais aussi, même les valeurs non pré-créées peuvent être identiques. Considérez ces exemples:
Et cela ne se limite pas aux
int
valeurs:Évidemment, CPython n'a pas de
float
valeur pré-créée pour42.23e100
. Alors, que se passe-t-il ici?Le compilateur CPython fusionnera des valeurs constantes de certains types connus comme immuables-
int
,float
,str
,bytes
, dans la même unité de compilation. Pour un module, l'ensemble du module est une unité de compilation, mais au niveau de l'interpréteur interactif, chaque instruction est une unité de compilation distincte. Étant donné quec
etd
sont définis dans des instructions distinctes, leurs valeurs ne sont pas fusionnées. Étant donné quee
etf
sont définis dans la même instruction, leurs valeurs sont fusionnées.Vous pouvez voir ce qui se passe en démontant le bytecode. Essayez de définir une fonction qui le fait
e, f = 128, 128
, puis appelezdis.dis
-la, et vous verrez qu'il n'y a qu'une seule valeur constante(128, 128)
Vous remarquerez peut-être que le compilateur est stocké en
128
tant que constante même s'il n'est pas réellement utilisé par le bytecode, ce qui vous donne une idée du peu d'optimisation du compilateur CPython. Ce qui signifie que les tuples (non vides) ne finissent pas par être fusionnés:Mettez ça dans une fonction,
dis
elle, et regarder lesco_consts
-Il y est un1
et un2
, deux(1, 2)
tuples qui partagent la même1
et ,2
mais ne sont pas identiques, et un((1, 2), (1, 2))
tuple qui a les deux tuples égales distinctes.Il y a une autre optimisation que CPython fait: l'internement de chaînes. Contrairement au pliage constant du compilateur, cela n'est pas limité aux littéraux de code source:
En revanche, il est limité au
str
type et aux chaînes de stockage interne de type "ascii compact", "compact" ou "legacy ready" , et dans de nombreux cas, seul "ascii compact" sera interné.En tout cas, les règles concernant les valeurs qui doivent être, peuvent être ou ne peuvent pas être distinctes varient d'une implémentation à l'autre, et entre les versions de la même implémentation, et peut-être même entre les exécutions du même code sur la même copie de la même implémentation .
Il peut être utile d'apprendre les règles d'un Python spécifique pour le plaisir. Mais cela ne vaut pas la peine de compter sur eux dans votre code. La seule règle sûre est:
x is y
, utiliserx == y
)x is not y
, utiliserx != y
)Ou, en d'autres termes, utilisez uniquement
is
pour tester les singletons documentés (commeNone
) ou qui ne sont créés qu'à un seul endroit dans le code (comme l'_sentinel = object()
idiome).la source
x is y
pour comparer, utiliserx == y
. De même, n'utilisez pasx is not y
, utilisezx != y
a=257; b=257
sur une seule lignea is b
Trueis
est l'opérateur d'égalité d'identité (fonctionnant commeid(a) == id(b)
); c'est juste que deux nombres égaux ne sont pas nécessairement le même objet. Pour des raisons de performances, certains petits entiers sont mémorisés , ils auront donc tendance à être les mêmes (cela peut être fait car ils sont immuables).L'
===
opérateur de PHP , d'autre part, est décrit comme vérifiant l'égalité et le type:x == y and type(x) == type(y)
selon le commentaire de Paulo Freitas. Cela suffira pour les nombres communs, mais diffère desis
classes qui définissent__eq__
de manière absurde:PHP autorise apparemment la même chose pour les classes "intégrées" (que je considère comme implémentées au niveau C, pas en PHP). Une utilisation légèrement moins absurde pourrait être un objet timer, qui a une valeur différente à chaque fois qu'il est utilisé comme un nombre. Tout à fait pourquoi vous voudriez émuler Visual Basic au
Now
lieu de montrer que c'est une évaluation avectime.time()
Je ne sais pas.Greg Hewgill (OP) a fait un commentaire clarifiant "Mon but est de comparer l'identité d'objet, plutôt que l'égalité de valeur. Sauf pour les nombres, où je veux traiter l'identité d'objet de la même manière que l'égalité de valeur."
Cela aurait encore une autre réponse, car nous devons classer les choses en nombres ou non, pour choisir si nous comparons avec
==
ouis
. CPython définit le protocole numérique , y compris PyNumber_Check, mais ce n'est pas accessible à partir de Python lui-même.Nous pourrions essayer d'utiliser
isinstance
tous les types de numéros que nous connaissons, mais cela serait inévitablement incomplet. Le module types contient une liste StringTypes mais pas de NumberTypes. Depuis Python 2.6, les classes numériques intégrées ont une classe de basenumbers.Number
, mais elle a le même problème:Au fait, NumPy produira des instances distinctes de faibles nombres.
Je ne connais pas vraiment de réponse à cette variante de la question. Je suppose que l'on pourrait théoriquement utiliser des ctypes pour appeler
PyNumber_Check
, mais même cette fonction a été débattue , et ce n'est certainement pas portable. Nous devrons simplement être moins précis sur ce que nous testons pour l'instant.En fin de compte, ce problème provient du fait que Python n'avait pas à l'origine d'arbre de type avec des prédicats comme Scheme
number?
ou la classe de type Haskell Num .is
vérifie l'identité de l'objet, pas l'égalité des valeurs. PHP a également une histoire colorée, où===
apparemment se comporteis
uniquement sur des objets en PHP5, mais pas PHP4 . Telles sont les difficultés croissantes de se déplacer entre les langues (y compris les versions d'une).la source
Cela se produit également avec les chaînes:
Maintenant, tout semble aller bien.
C'est attendu aussi.
Maintenant, c'est inattendu.
la source
'xx'
est comme prévu, telle quelle'xxx'
, mais'x x'
ne l'est pas.xx
part dans votre session Python, cette chaîne est déjà internée; et il peut y avoir une heuristique qui le fait si elle ressemble à un nom. Comme pour les chiffres, cela peut être fait car ils sont immuables. docs.python.org/2/library/functions.html#intern guilload.com/python-string-interningQuoi de neuf dans Python 3.8: changements dans le comportement de Python :
la source