9 votes

Pourquoi l'opérateur `is` se comporte-t-il différemment dans un script et dans le REPL ?

En python, deux codes donnent des résultats différents :

a = 300
b = 300
print (a==b)
print (a is b)      ## print True
print ("id(a) = %d, id(b) = %d"%(id(a), id(b))) ## They have same address

Mais en mode shell (mode interactif) :

>>> a = 300
>>> b = 300
>>> a is b
False
>>> id(a)
4501364368
>>> id(b)
4501362224

L'opérateur "is" donne des résultats différents.

18voto

wim Points 35274

Lorsque vous exécutez du code dans un .py script, l'option fichier entier es compilé en un objet de code antes de l'exécuter. Dans ce cas, CPython est capable d'effectuer certaines optimisations - comme la réutilisation de la même instance pour l'entier 300.

Vous pouvez également reproduire cela dans la REPL, en exécutant le code dans un contexte plus proche de l'exécution d'un script :

>>> source = """\ 
... a = 300 
... b = 300 
... print (a==b) 
... print (a is b)## print True 
... print ("id(a) = %d, id(b) = %d"%(id(a), id(b))) ## They have same address 
... """
>>> code_obj = compile(source, filename="myscript.py", mode="exec")
>>> exec(code_obj) 
True
True
id(a) = 140736953597776, id(b) = 140736953597776

Certaines de ces optimisations sont assez agressives. Vous pourriez modifier la ligne script. b = 300 en le remplaçant par b = 150 + 150 et CPython se "plierait" toujours b dans la même constante. Si vous êtes intéressé par ces détails de mise en œuvre, consultez la page peephole.c et Ctrl+F pour PyCode_Optimize et toute information sur la "table des constes".

En revanche, lorsque vous exécutez le code ligne par ligne directement dans la REPL, il s'exécute dans un contexte différent. Chaque ligne est compilée en mode "simple" et cette optimisation n'est pas disponible.

>>> scope = {} 
>>> lines = source.splitlines()
>>> for line in lines: 
...     code_obj = compile(line, filename="<I'm in the REPL, yo!>", mode="single")
...     exec(code_obj, scope) 
...
True
False
id(a) = 140737087176016, id(b) = 140737087176080
>>> scope['a'], scope['b']
(300, 300)
>>> id(scope['a']), id(scope['b'])
(140737087176016, 140737087176080)

6voto

jszakmeister Points 8323

Il y a en fait deux choses à savoir à propos de CPython et de son comportement ici. Premièrement, les petits entiers de l'ordre de [-5, 256] sont internalisés en interne. Ainsi, toute valeur se situant dans cette fourchette partagera le même identifiant, même au niveau de la REPL :

>>> a = 100
>>> b = 100
>>> a is b
True

Puisque 300 > 256, il n'est pas interné :

>>> a = 300
>>> b = 300
>>> a is b
False

Deuxièmement, dans un script, les éléments littéraux sont placés dans une section constante du fichier code compilé. Python est suffisamment intelligent pour réaliser que, puisque les deux types de caractères a y b se réfèrent au texte littéral 300 et que 300 est un objet immuable, il peut simplement de référencer le même emplacement constant. Si vous modifiez votre script et que vous l'écrivez comme suit

def foo():
    a = 300
    b = 300
    print(a==b)
    print(a is b)
    print("id(a) = %d, id(b) = %d" % (id(a), id(b)))

import dis
dis.disassemble(foo.__code__)

Le début de la sortie ressemble à ceci :

2           0 LOAD_CONST               1 (300)
            2 STORE_FAST               0 (a)

3           4 LOAD_CONST               1 (300)
            6 STORE_FAST               1 (b)

...

Comme vous pouvez le voir, CPython charge le fichier a y b en utilisant le même créneau constant. Cela signifie que a y b se réfèrent maintenant au même objet (parce qu'ils référence au même slot) et c'est pourquoi a is b es True dans le script mais mais pas dans le REPL.

Vous pouvez également observer ce comportement dans la REPL, si vous intégrez vos instructions dans une fonction :

>>> import dis
>>> def foo():
...   a = 300
...   b = 300
...   print(a==b)
...   print(a is b)
...   print("id(a) = %d, id(b) = %d" % (id(a), id(b)))
...
>>> foo()
True
True
id(a) = 4369383056, id(b) = 4369383056
>>> dis.disassemble(foo.__code__)
  2           0 LOAD_CONST               1 (300)
              2 STORE_FAST               0 (a)

  3           4 LOAD_CONST               1 (300)
              6 STORE_FAST               1 (b)
# snipped...

En conclusion : bien que CPython effectue parfois ces optimisations, vous ne devriez pas vraiment compter dessus - il s'agit vraiment d'un détail d'implémentation, qui a été modifié au fil du temps (CPython ne le faisait auparavant que pour les entiers jusqu'à 100, par exemple). Si vous comparez des nombres, utilisez == . :-)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X