L'opérateur «is» se comporte de façon inattendue avec des entiers

509

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' isopé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?

Greg Hewgill
la source
1
Jetez un œil ici > 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> récupérez simplement une référence à l'objet existant.
user5319825
2
Ceci est un détail d'implémentation spécifique à CPython et un comportement indéfini, à utiliser avec précautions
ospider

Réponses:

393

Regarde ça:

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

Voici ce que j'ai trouvé dans la documentation Python 2, "Plain Integer Objects" (C'est la même chose pour Python 3 ):

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. :-)

Cybis
la source
46
Quelqu'un sait-il comment cette plage (-5, 256) a été choisie? je ne serais pas trop surpris si c'était (0, 255) ou même (-255, 255), mais une plage de 262 nombres commençant à -5 semble étonnamment arbitraire.
Woodrow Barlow
6
@WoodrowBarlow: Le -5 est juste une heuristique pour capturer les espaces réservés négatifs communs, je pense. 0..255 couvre les tableaux de valeurs à un octet. C'est 256 qui est mystérieux, mais je suppose que c'est pour (dés) assembler des entiers dans / à partir d'octets.
Davis Herring
3
D'après ce que je comprends, la gamme a été choisie en examinant les valeurs couramment utilisées dans plusieurs projets (et plusieurs langues).
Tony Suffolk 66
9
Selon reddit.com/r/Python/comments/18leav/… , la plage était auparavant de [-5,100]. Il a été étendu pour inclure la plage complète des valeurs d'octets - plus 256, car il s'agit probablement d'un nombre commun.
mwfearnley
2
@Ashwani essayez de lire les commentaires juste à côté de votre commentaire, posté deux ans avant le vôtre, et vous trouverez la réponse à votre question.
jbg
116

L'opérateur «is» de Python se comporte de façon inattendue avec des entiers?

En résumé - permettez-moi de souligner: ne pas utiliser ispour 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:

>>> 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 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 :

Les opérateurs iset le is nottest d'identité d'objet: x is yest vrai si et seulement si x et y sont le même objet. x is not ydonne la valeur de vérité inverse.

Et donc les éléments suivants sont équivalents.

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

De la documentation :

id Renvoie «l'identité» d'un objet. Il s'agit d'un entier (ou entier long) qui est garanti unique et constant pour cet objet pendant sa durée de vie. Deux objets dont la durée de vie ne se chevauche pas peuvent avoir la même id()valeur.

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 :

Les comparaisons avec des singletons comme Nonedevraient toujours être faites avec isou is not, jamais avec les opérateurs d'égalité.

La question

Vous posez et énoncez la question suivante (avec code):

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

Ce n'est pas un résultat attendu. Pourquoi est-il attendu? Cela signifie seulement que les entiers évalués à 256référencés par les deux aet bsont 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.

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

On dirait que nous avons maintenant deux instances distinctes d'entiers avec la valeur de 257en 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.

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

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 que

isvérifie que les iddeux objets sont identiques. Dans CPython, l' idemplacement 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:

>>> a is b

est le même que

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

Pourquoi voudrions-nous utiliser isalors?

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érifier is, mais ceux-ci sont relativement rares. Voici un exemple (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 donc nous voyons, avec iset une sentinelle, nous pouvons faire la différence entre quand barest appelé sans argument et quand il est appelé avec None. Ce sont les cas d' utilisation primaire pour is- ne pas l' utiliser pour tester l'égalité des entiers, des chaînes, tuples, ou d' autres choses comme celles - ci.

Aaron Hall
la source
"Ce sont les principaux cas d'utilisation pour 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 comparables is. 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 0c'est un détail d'implémentation).
Alexey
@Alexey sonne comme si vous aviez besoin d'énumérations? stackoverflow.com/questions/37601644/…
Aaron Hall
Peut-être, merci, ne les connaissais pas. Cela pourrait être un ajout approprié pour répondre à l'OMI.
Alexey
Peut-être que l'utilisation d'un certain nombre d'objets stupides comme la sentinelle dans votre réponse serait une solution plus légère ...
Alexey
Les énumérations @Alexey sont dans la bibliothèque standard de Python 3, et cela encouragerait probablement votre code à être un peu plus significatif que les sentinelles nues.
Aaron Hall
60

Cela dépend si vous cherchez à voir si 2 choses sont égales ou le même objet.

isvé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'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é des objets arbitraires. Vous pouvez spécifier le comportement avec les attributs __eq__et __ne__.

JimB
la source
Bravo pour avoir expliqué comment comparer des objets arbitraires, comme l'OP l'a demandé !!
Joooeey
54

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 intobjet 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 Objectssous - répertoire de l' arborescence du répertoire du code source principal .

PyLong_FromLongtraite des longobjets, 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_intsi la valeur ivalsatisfait la condition:

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

Alors, quels sont NSMALLNEGINTSet 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_intdans 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_intsressemble 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 intdans 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 PyLongObjectrepré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 isreviendra True.

Dimitris Fasarakis Hilliard
la source
37

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.

ange
la source
19

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 <= 255soient traités comme des littéraux et tout ce qui précède est traité différemment!

Amit
la source
1
C'est parce que les objets représentant des valeurs de -5 à +256 sont créés au démarrage - et donc toute utilisation de ces valeurs est utilisée pour un objet préconstruit. Presque toutes les références à des entiers en dehors de cette plage créent un nouvel objet interne chaque fois qu'elles sont référencées. Je pense que l'utilisation du terme littéral est source de confusion - littéral se réfère normalement à toute valeur tapée dans un morceau de code - donc tous les nombres dans le code source sont des littéraux.
Tony Suffolk 66
13

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.

babbageclunk
la source
12

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, stret bytes, et certaines chaînes courtes (dans CPython 3.6, il s'agit des 256 chaînes Latin-1 à un seul caractère). 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 cela ne se limite pas aux intvaleurs:

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

Évidemment, CPython n'a pas de floatvaleur pré-créée pour 42.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é que cet dsont définis dans des instructions distinctes, leurs valeurs ne sont pas fusionnées. Étant donné que eet fsont 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 appelez dis.dis-la, et vous verrez qu'il n'y a qu'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 remarquerez peut-être que le compilateur est stocké en 128tant 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:

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

Mettez ça dans une fonction, diselle, et regarder les co_consts-Il y est un 1et un 2, deux (1, 2)tuples qui partagent la même 1et , 2mais 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:

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

En revanche, il est limité au strtype 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:

  • N'écrivez pas de code qui suppose que deux valeurs immuables égales mais créées séparément sont identiques (ne pas utiliser x is y, utiliser x == y)
  • N'écrivez pas de code qui suppose que deux valeurs immuables égales mais créées séparément sont distinctes (ne pas utiliser x is not y, utiliser x != y)

Ou, en d'autres termes, utilisez uniquement ispour tester les singletons documentés (comme None) ou qui ne sont créés qu'à un seul endroit dans le code (comme l' _sentinel = object()idiome).

abarnert
la source
Le conseil le moins cryptique est tout simplement: ne pas utiliser x is ypour comparer, utiliser x == y. De même, n'utilisez pas x is not y, utilisezx != y
smci
Donc, en regardant cette question , pourquoi est a=257; b=257sur une seule ligne a is bTrue
Joe
8

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 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 des isclasses qui définissent __eq__de manière absurde:

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

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 Nowlieu de montrer que c'est une évaluation avec time.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 == ou is. 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 base numbers.Number, mais elle a le même problème:

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

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 . isvérifie l'identité de l'objet, pas l'égalité des valeurs. PHP a également une histoire colorée, où ===apparemment se comporte isuniquement 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).

Yann Vernier
la source
4

Cela se produit également avec les chaînes:

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

Maintenant, tout semble aller bien.

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

C'est attendu 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)

Maintenant, c'est inattendu.

sobolevn
la source
Arrivé sur ce - convenu, que encore plus bizarre. J'ai donc joué avec, et c'est encore plus étrange - lié à l'espace. Par exemple, la chaîne 'xx'est comme prévu, telle quelle 'xxx', mais 'x x'ne l'est pas.
Brian
2
C'est parce qu'il ressemble à un symbole s'il n'y a pas d'espace dedans. Les noms sont automatiquement internés, donc s'il y a quelque chose nommé quelque xxpart 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-interning
Yann Vernier
3

Quoi de neuf dans Python 3.8: changements dans le comportement de Python :

Le compilateur produit désormais un SyntaxWarning lorsque les vérifications d'identité ( iset is not) sont utilisées avec certains types de littéraux (par exemple des chaînes, des entiers). Ceux-ci peuvent souvent fonctionner par accident dans CPython, mais ne sont pas garantis par la spécification du langage. L'avertissement conseille aux utilisateurs d'utiliser à la place des tests d'égalité ( == et !=).

cclauss
la source