l'opérateur "is" se comporte de façon inattendue avec des entiers

pourquoi ce qui suit se comporte-t-il de façon 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 quelques différentes versions de Python, il semble que Python 2.3.3 montre le comportement ci-dessus entre 99 et 100.

basé sur ce qui précède, je peux émettre l'hypothèse que Python est implémenté en interne de sorte que les" petits "entiers sont stockés d'une manière différente des plus grands entiers et l'opérateur is peut faire la différence. Pourquoi l' l'abstraction fuyante? 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'ils sont des nombres ou non?

391
demandé sur Jim Fasarakis Hilliard 2008-11-20 21:21:16
la source

11 ответов

regardez ça:

>>> a = 256
>>> b = 256
>>> id(a)
9987148
>>> id(b)
9987148
>>> a = 257
>>> b = 257
>>> id(a)
11662816
>>> id(b)
11662828

EDIT: Voici ce que j'ai trouvé dans le Python 2 de la documentation, "Plaine Entier les Objets" (C'est la même chose pour Python 3 ):

L'implémentation actuelle maintient un tableau d'objets entiers pour tous les nombres entiers entre -5 et 256, quand vous créer un int dans cette gamme vous en fait juste en arrière d'une référence à l'objet existant. De sorte qu'il devrait être possibilité de changer la valeur de 1. Je soupçonner le comportement de Python dans ce cas n'est pas défini. :- )

308
répondu Cybis 2016-05-31 21:45:36
la source

Python "est" l'opérateur se comporte de manière inattendue avec des entiers?

en résumé - permettez-moi de souligner: ne pas utiliser is pour comparer des entiers.

ce n'est pas un comportement sur lequel vous devriez avoir des attentes.

utilisez plutôt == et != pour comparer l'égalité et l'inégalité, respectivement. Pour exemple:

>>> a = 1000
>>> a == 1000       # Test integers like this,
True
>>> a != 5000       # or this!
True
>>> a is 1000       # Don't do this! - Don't use `is` to test integers!!
False

explication

pour savoir cela, vous devez savoir ce qui suit.

tout d'abord, que fait is ? C'est un opérateur de comparaison. De la documentation :

Les opérateurs is et is not test de l'identité de l'objet: x is y est vrai si et seulement si x et y sont le même objet. x is not y les rendements le inversez la valeur de vérité.

et donc ce qui suit sont équivalents.

>>> a is b
>>> id(a) == id(b)

De la documentation :

id De retour de "l'identité" d'un objet. C'est un entier (ou long entier) qui est garanti pour être unique et constante pour cet objet au cours de sa durée de vie. Deux objets dont les vies ne se chevauchent pas peut ont la même valeur id() .

on Remarque que le fait que l'id d'un objet dans Disponible (l'implémentation de référence de Python) est l'emplacement dans la mémoire est un détail d'implémentation. D'autres implémentations de Python (comme Jython ou IronPython) pourraient facilement avoir une implémentation différente pour id .

Quelle est donc l'utilisation de is ? PEP8 décrit :

Comparaisons à des singletons comme None devrait toujours être fait avec is ou is not , jamais les opérateurs d'égalité.

La Question

vous posez, et déclarez, la question suivante (avec code):

pourquoi les suivants se comportent-ils de façon inattendue en Python?

>>> a = 256
>>> b = 256
>>> a is b
True           # This is an expected result

C'est et non un résultat attendu. Pourquoi est-il prévu? Cela signifie seulement que les entiers évalués à 256 référencés par a et b sont la même instance d'entier. Les entiers sont immuables en Python, donc ils ne peuvent pas changer. Cela ne devrait avoir aucun impact sur aucun code. Il ne devrait pas être prévu. C'est qu'un détail d'implémentation.

mais peut-être devrions-nous être heureux qu'il n'y ait pas une nouvelle instance séparée dans la mémoire chaque fois qu'on déclare une valeur égale 256.

>>> a = 257
>>> b = 257
>>> a is b
False          # What happened here? Why is this False?

semble comme nous avons maintenant deux instances séparées d'entiers avec la valeur de 257 dans la mémoire. Puisque les entiers sont immuables, cela gaspille la mémoire. Espérons que nous ne sommes pas perdre beaucoup de lui. Nous ne serons probablement pas. Mais ce comportement n'est pas garanti.

>>> 257 is 257
True           # Yet the literal numbers compare properly

Eh bien, cela ressemble à votre mise en œuvre particulière de Python essaie d'être intelligent et de ne pas créer des entiers de valeur redondante en mémoire à moins qu'il ne le doive. Vous semblez indiquer que vous utilisez l'implémentation de référence de Python, qui est CPython. Bon pour Disponible.

il pourrait être encore mieux si CPython pouvait le faire globalement, s'il pouvait le faire à moindre coût (comme il y aurait un coût dans la recherche), peut-être une autre mise en œuvre pourrait.

mais quant à l'impact sur le code, vous ne devriez pas se soucier si un entier est une instance particulière d'un entier. Vous ne devez vous soucier que de la valeur de cette instance, et vous utiliserez les opérateurs de comparaison normaux pour cela, i.e. == .

Ce is ne

is vérifie que le id de deux objets est le même. Dans CPython, le id est l'emplacement dans la mémoire, mais il pourrait être un autre numéro d'identification unique dans une autre implémentation. Pour reformuler ceci avec le code:

>>> a is b

est le même que

>>> id(a) == id(b)

Pourquoi utiliser is alors?

ceci peut être une vérification très rapide par rapport à say, vérifiant si deux très longues chaînes sont égales en valeur. Mais puisqu'il s'applique à l'unicité de l'objet, nous avons donc limité des cas d'utilisation. En fait, nous voulons surtout l'utiliser pour vérifier les None , qui est un singleton (une seule instance existante dans un seul endroit mémoire.) Nous pourrions créer d'autres singletons s'il y a possibilité de les confondre, ce que nous pourrions vérifier avec is , mais ceux-ci sont relativement rares. Voici un exemple (qui fonctionnera en Python 2 et 3) par exemple

SENTINEL_SINGLETON = object() # this will only be created one time.

def foo(keyword_argument=None):
    if keyword_argument is None:
        print('no argument given to foo')
    bar()
    bar(keyword_argument)
    bar('baz')

def bar(keyword_argument=SENTINEL_SINGLETON):
    # SENTINEL_SINGLETON tells us if we were not passed anything
    # as None is a legitimate potential argument we could get.
    if keyword_argument is SENTINEL_SINGLETON:
        print('no argument given to bar')
    else:
        print('argument to bar: {0}'.format(keyword_argument))

foo()

qui imprime:

no argument given to foo
no argument given to bar
argument to bar: None
argument to bar: baz

et ainsi nous voyons, avec is et une sentinelle, nous sommes en mesure de différencier quand bar est appelé sans arguments et quand il est appelé avec None . Ce sont le principal cas d'utilisation pour is - ne pas l'utiliser pour tester l'égalité des entiers, des chaînes, tuples, ou d'autres choses comme celles-ci.

64
répondu Aaron Hall 2017-09-09 23:02:03
la source

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 et pas seulement de l'équivalent. Les petits ints pointent probablement vers le même emplacement mémoire pour l'efficacité de l'espace

In [29]: a = 3
In [30]: b = 3
In [31]: id(a)
Out[31]: 500729144
In [32]: id(b)
Out[32]: 500729144

Vous devez utiliser == pour comparer l'égalité d'objets arbitraires. Vous pouvez spécifier le comportement avec les attributs __eq__ , et __ne__ .

55
répondu JimB 2017-04-24 05:20:18
la source

comme vous pouvez le vérifier dans fichier source intobject.c , Python cache de petits entiers pour l'efficacité. Chaque fois que vous créez une référence à un petit entier, vous faites référence au petit entier caché, pas à un nouvel objet. 257 n'est pas un petit entier, donc il est calculé comme un objet différent.

il est préférable d'utiliser == à cet effet.

36
répondu Angel 2017-04-24 05:20:25
la source

je suis en retard mais, vous voulez une source avec votre réponse? *

la bonne chose à propos de CPython est que vous pouvez réellement voir la source pour cela. Je vais utiliser les liens pour la version 3.5 pour l'instant; trouver les liens 2.x correspondants est trivial.

dans CPython, la fonction C-API qui gère la création d'un nouvel objet int 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 entre -5 et 256, lorsque vous créez un int dans cette plage, vous obtenez en fait juste une référence à l'objet existant . Il devrait donc être possible de modifier la valeur de 1. Je pense que le comportement de Python dans ce cas n'est pas défini. :- )

Je ne sais pas pour vous mais je vois ça et je me dis: trouvons ce tableau!

si vous n'avez pas bricolé avec le C code de mise en œuvre de CPython vous devriez , tout est assez organisé et lisible. Pour notre cas, nous devons regarder dans le Objects/ sous-répertoire du répertoire principal de code source .

PyLong_FromLong offres spéciales avec les objets long donc il ne devrait pas être difficile de déduire que nous devons regarder à 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 pas peur, la fonction que nous recherchons refroidit à line 230 attendant que nous vérifions. C'est une fonction minuscule 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 C master-code-haxxorz mais nous ne sommes pas non plus muets, nous pouvons voir que CHECK_SMALL_INT(ival); nous espionne tous de façon séduisante; nous pouvons comprendre qu'il a quelque chose à voir avec cela. voyons ça:

#define CHECK_SMALL_INT(ival) \
    do if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) { \
        return get_small_int((sdigit)ival); \
    } while(0)

donc c'est une macro qui appelle la fonction get_small_int si la valeur ival satisfait à la condition:

if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS)

alors que sont NSMALLNEGINTS et NSMALLPOSINTS ? Si vous avez deviné macros vous n'obtenez rien parce que ce n'était pas une question difficile.. quoi qu'il en soit, les voici :

#ifndef NSMALLPOSINTS
#define NSMALLPOSINTS           257
#endif
#ifndef NSMALLNEGINTS
#define NSMALLNEGINTS           5
#endif

donc notre condition est if (-5 <= ival && ival < 257) appel get_small_int .

il N'y a pas d'autre endroit où aller que de continuer notre voyage en regardant get_small_int dans toute sa gloire (Eh bien, nous allons juste regarder son corps parce que c'était les choses intéressantes sont):

PyObject *v;
assert(-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS);
v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];
Py_INCREF(v);

OK, déclarer un PyObject , affirmer que la condition précédente tient et exécuter la tâche:

v = (PyObject *)&small_ints[ival + NSMALLNEGINTS];

small_ints ressemble beaucoup à ce tableau que nous avons cherché.. et, il est! nous aurions pu simplement lire cette fichue documentation et nous l'aurions toujours su! :

/* 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];

donc yup, c'est notre homme. Quand vous voulez créer un nouveau int dans la gamme [NSMALLNEGINTS, NSMALLPOSINTS) vous obtiendrez juste 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, l'émission id() directement ou le contrôle d'identité avec is sur elle retournera exactement la même chose.

mais, quand sont-ils alloués??

lors de l'initialisation dans _PyLong_Init Python fera volontiers entrer dans une boucle pour faire ceci pour vous:

for (ival = -NSMALLNEGINTS; ival <  NSMALLPOSINTS; ival++, v++) {
    // Look me up!
}

j'espère que mon explication vous a fait C (jeu de mots évidemment intenté) les choses clairement maintenant.


mais, 257 c'est 257? Qu'est-ce?

c'est en fait plus facile à expliquer, et j'ai déjà essayé de le faire ; c'est dû au fait que Python va exécuter cette instruction interactive:

>>> 257 is 257

en bloc. Lors de la complilation de cette déclaration, CPython verra que vous avez deux correspondances littérales et utiliserez le même PyLongObject représentant 257 . Vous pouvez le voir si vous faites la compilation vous-même et examinez son contenu:

>>> codeObj = compile("257 is 257", "blah!", "exec")
>>> codeObj.co_consts
(257, None)

quand CPython fait l'opération; il va maintenant charger le même objet exact:

>>> import dis
>>> dis.dis(codeObj)
  1           0 LOAD_CONST               0 (257)   # dis
              3 LOAD_CONST               0 (257)   # dis again
              6 COMPARE_OP               8 (is)

Donc is sera retournez True .


* -- je vais essayer de le dire d'une manière plus introductive afin que la plupart puissent suivre.

32
répondu Jim Fasarakis Hilliard 2017-09-01 02:53:19
la source

je pense que vos hypothèses sont correctes. Expérience avec id (identité de l'objet):

In [1]: id(255)
Out[1]: 146349024

In [2]: id(255)
Out[2]: 146349024

In [3]: id(257)
Out[3]: 146802752

In [4]: id(257)
Out[4]: 148993740

In [5]: a=255

In [6]: b=255

In [7]: c=257

In [8]: d=257

In [9]: id(a), id(b), id(c), id(d)
Out[9]: (146349024, 146349024, 146783024, 146804020)

il semble que les nombres <= 255 sont traités comme littéraux et tout ce qui est au-dessus est traité différemment!

18
répondu Amit 2017-02-08 13:12:22
la source

pour les objets à valeur immuable, comme les ints, les chaînes ou les datetimes, l'identité de l'objet n'est pas particulièrement utile. Il est préférable de penser à propos de 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 des références multiples au même objet ou à plusieurs objets.

12
répondu babbageclunk 2008-11-21 04:58:53
la source

is est l'opérateur d'égalité d'identité (fonctionnant comme id(a) == id(b) ); c'est juste que deux nombres égaux ne sont pas nécessairement le même objet. Pour des raisons de performance certains petits entiers se trouvent être memoized de sorte qu'ils auront tendance à être les mêmes (cela peut être fait car ils sont immuables).

opérateur PHP === , d'autre part, est décrit comme vérification égalité et type: x == y and type(x) == type(y) selon le commentaire de Paulo Freitas. Cela suffira pour les numéros communs, mais différera de is pour les classes qui définissent __eq__ d'une manière absurde:

class Unequal:
    def __eq__(self, other):
        return False

PHP permet apparemment la même chose pour les classes "intégrées" (ce qui signifie implémenté au niveau C, pas en PHP). Une utilisation un peu 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 voulez imiter Visual Basic Now au lieu de montrer qu'il s'agit d'une évaluation avec time.time() Je ne sais pas.

Greg Hewgill (OP) a fait un commentaire clarifiant "mon but est de comparer l'identité de l'objet, plutôt que l'égalité de valeur. Sauf pour les nombres, où je veux traiter de l'identité de l'objet même que l'égalité de valeur."

cela aurait une autre réponse, car nous devons catégoriser les choses comme des nombres ou non, pour choisir si nous comparons avec == ou is . CPython définit le number protocol , y compris PyNumber_Check, mais ce n'est pas accessible depuis Python lui-même.

nous pourrions essayer d'utiliser isinstance avec tous les types de nombres que nous connaissons, mais cela serait inévitablement incomplet. Le module types contient une liste de StringTypes mais pas de NumberTypes. Depuis Python 2.6, les classes de nombre construites ont une classe de base numbers.Number , mais il a le même problème:

import numpy, numbers
assert not issubclass(numpy.int16,numbers.Number)
assert issubclass(int,numbers.Number)

soit dit en passant, NumPy va produire des instances séparées de faibles nombres.

Je ne connais pas vraiment de réponse à cette variante de la question. Je suppose qu'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 devons simplement être moins particulier A propos de ce qu'on teste pour l'instant.

à la fin, ce numéro provient de Python n'ayant pas à l'origine un arbre type avec des prédicats comme Scheme number? , ou Haskell's type class 1519580920" Num . is vérifie l'identité de l'objet, pas l'égalité de valeur. PHP a une histoire colorée aussi bien, où === se comporte apparemment comme is seulement sur les objets en PHP5, mais pas en PHP4 . Telles sont les douleurs de croissance de se déplacer à travers les langues (y compris les versions d'un).

8
répondu Yann Vernier 2015-11-12 14:52:54
la source

il arrive aussi avec des cordes:

>>> s = b = 'somestr'
>>> s == b, s is b, id(s), id(b)
(True, True, 4555519392, 4555519392)

maintenant tout semble bien.

>>> s = 'somestr'
>>> b = 'somestr'
>>> s == b, s is b, id(s), id(b)
(True, True, 4555519392, 4555519392)

c'est prévu aussi.

>>> s1 = b1 = 'somestrdaasd ad ad asd as dasddsg,dlfg ,;dflg, dfg a'
>>> s1 == b1, s1 is b1, id(s1), id(b1)
(True, True, 4555308080, 4555308080)

>>> s1 = 'somestrdaasd ad ad asd as dasddsg,dlfg ,;dflg, dfg a'
>>> b1 = 'somestrdaasd ad ad asd as dasddsg,dlfg ,;dflg, dfg a'
>>> s1 == b1, s1 is b1, id(s1), id(b1)
(True, False, 4555308176, 4555308272)

c'est inattendu.

4
répondu sobolevn 2015-10-14 18:53:05
la source

regardez ici

L'implémentation actuelle conserve un tableau d'objets entiers pour tous entiers entre -5 et 256, lorsque vous créez un int dans cette gamme vous en fait juste en arrière d'une référence à l'objet existant.

3
répondu user5319825 2016-02-18 15:46:27
la source

il y a une autre question qui n'est pas mentionnée dans les réponses existantes. Python est autorisé à fusionner deux valeurs immuables, et les petites valeurs int pré-créées ne sont pas la seule façon que cela puisse se produire. Une implémentation Python n'est jamais garantie pour faire cela, mais ils le font tous pour plus que de petits ints.


pour une chose, il y a d'autres valeurs pré-créées, comme le vide tuple , str , et bytes , et quelques courtes cordes (en CPython 3.6, c'est les 256 caractères latins-1 cordes). Par exemple:

>>> a = ()
>>> b = ()
>>> a is b
True

mais aussi, même les valeurs non pré-créées peuvent être identiques. Considérez ces exemples:

>>> c = 257
>>> d = 257
>>> c is d
False
>>> e, f = 258, 258
>>> e is f
True

et ce n'est pas limité aux valeurs int :

>>> g, h = 42.23e100, 42.23e100
>>> g is h
True

évidemment, CPython ne vient pas avec une valeur pré-créée float pour 42.23e100 . Donc, ce qui se passe ici?

le compilateur CPython va fusionner des valeurs constantes de certains types connus-immuables comme int , float , str , bytes , dans la même unité de compilation. Pour un module, le module entier est une unité de compilation, mais à l'interpréteur interactif, chaque déclaration est une unité de compilation distincte. Puisque c et d sont définis dans des énoncés séparés, leurs valeurs ne sont pas fusionnées. Depuis e et f sont définis dans le même énoncé, leurs valeurs sont fusionnées.


vous pouvez voir ce qui se passe en démontant le bytecode. Essayez de définir une fonction qui fait e, f = 128, 128 et puis appeler dis.dis sur elle, et vous verrez qu'il y a une seule valeur constante (128, 128)

>>> def f(): i, j = 258, 258
>>> dis.dis(f)
  1           0 LOAD_CONST               2 ((128, 128))
              2 UNPACK_SEQUENCE          2
              4 STORE_FAST               0 (i)
              6 STORE_FAST               1 (j)
              8 LOAD_CONST               0 (None)
             10 RETURN_VALUE
>>> f.__code__.co_consts
(None, 128, (128, 128))
>>> id(f.__code__.co_consts[1], f.__code__.co_consts[2][0], f.__code__.co_consts[2][1])
4305296480, 4305296480, 4305296480

vous pouvez noter que le compilateur a stocké 128 comme une constante même si elle n'est pas réellement utilisée par le bytecode, ce qui vous donne une idée du peu d'optimisation Que fait le compilateur de CPython. Ce qui signifie que (non vide) tuples réellement ne finissent pas fusionné:

>>> k, l = (1, 2), (1, 2)
>>> k is l
False

mettez cela dans une fonction, dis il, et regardez le co_consts - il y a un 1 et un 2 , deux (1, 2) tuples qui partagent le même 1 et 2 mais ne sont pas identiques, et un ((1, 2), (1, 2)) tuple qui a les deux tuples égaux.


il y a une autre optimisation que CPython fait: l'internage des cordes. Contrairement au compilateur constant folding, ceci n'est pas limité aux sources du code littéral:

>>> m = 'abc'
>>> n = 'abc'
>>> m is n
True

d'autre part, il est limité au type str , et aux chaînes de de type de stockage interne "ASCII compact", "compact", ou "prêt à l'héritage " , et dans de nombreux cas seulement "ASCII compact" va être interné.


en tout cas, les règles pour ce que les valeurs doivent être, peuvent être ou ne peuvent pas être distinctes varient de la mise en œuvre à la mise en œuvre, et entre les versions de la même mise en œuvre, et peut-être même entre les passages du même code sur la même copie de la même mise en œuvre.

il peut être intéressant d'apprendre les règles pour un Python spécifique pour le plaisir de celui-ci. Mais c'est pas la peine de compter sur eux dans votre code. La seule règle de sécurité est:

  • N'écrivez pas de code qui suppose que deux valeurs immuables égales mais créées séparément sont identiques.
  • N'écrivez pas de code qui suppose que deux valeurs immuables égales mais créées séparément sont distinctes.

ou, en d'autres termes, n'utiliser que is pour tester les singletons documentés (comme None ) ou qui ne sont créés qu'à un seul endroit dans le code (comme l'idiome _sentinel = object() ).

2
répondu abarnert 2018-03-25 06:48:45
la source

Autres questions sur python int identity operators comparison