Une explication sur is

Le
thomas
Bonjour,

J'ai une question à propose du comportement de Python sur la façon de
créer des objets et de les référencer.

Situation 1 :

>>> a = 5
>>> b = 5

>>> a is b
True


Jusque-là je me dis que lorsque je fais a = 5, comme c'est un objet qui
doit déjà exister (j'imagine que c'est un objet assez courant, utilisé
par Python, qui est créé à son lancement et qu'il doit même être déjà
référencé), il n'est pas créé donc a va référencer l'objet 5 déjà
existant. Ensuite quand je fais b = 5, il se passe la même chose et b va
référencer le même objet 5 déjà existant. Du coup, a et b référencent le
même objet, ce qui explique que a is b renvoie True.


Par contre, ce que je ne comprends pas c'est la chose suivante :

Situation 2 :

>>> a = 500
>>> b = 500
>>> a is b
False

Lorsque je fais a = 500, si l'objet 500 n'existe pas, il est créé puis
référencé par a. Ensuite en faisant b = 500, l'objet 500 existant déjà
(il a, au pire, été créé en faisant a = 500), l'objet 500 ne devrait
donc pas être créé et b devrait référencer là encore le même objet 500
déjà existant. Du coup a is b devrait renvoyer True mais ce n'est pas le
cas.

Où mon raisonnement est-il faux ?

D'avance, je vous remercie.


Thomas
Vos réponses
Gagnez chaque mois un abonnement Premium avec GNT : Inscrivez-vous !
Trier par : date / pertinence
Francois Lafont
Le #26546014
Hello,
On 5/10/20 11:39 AM, thomas wrote:
J'ai une question à propose du comportement de Python sur la façon de créer des objets et de les référencer.
Situation 1 :
a = 5
b = 5





a is b





True
Jusque-là je me dis que lorsque je fais a = 5, comme c'est un objet qui doit déjà exister (j'imagine que c'est un objet assez courant, utilisé par Python, qui est créé à son lancement et qu'il doit même être déjà référencé), il n'est pas créé donc a va référencer l'objet 5 déjà existant. Ensuite quand je fais b = 5, il se passe la même chose et b va référencer le même objet 5 déjà existant. Du coup, a et b référencent le même objet, ce qui explique que a is b renvoie True.

Perso, de ce petit test, je pense qu'on ne peut pas affirmer que l'objet « 5 »
existait avant même l'instruction « a = 5 » (même si c'est peut-être tout à fait
vrai). On peut juste dire qu'effectivement, au moment de « b = 5 », Python a
fait pointer b vers l'objet « 5 » référencé par la variable a et on peut penser
que c'est dans un souci d'optimisation de la mémoire. Et, au passage, cette
référence du même objet entre a et b est possible car l'objet référencé « 5 »
est immutable. Cela ne pourrait pas se produire avec par exemple :
a = []
b = []
a is b # => renverra forcément False.
vu que [] n'est pas un objet immutable, il est mutable. En effet, rien dans le
code n'indique explicitement que a et b référencent le même objet. Donc il n'y
a aucune raison pour qu'une modification de l'objet référencé par a modifie
l'état de l'objet référencé par b. Donc a et b sont nécessairement deux objets
distincts de la mémoire.
Mais tout ceci relève du détail d'implémentation à mon avis et il ne faut pas
trop s'embêter avec cela. Personnellement, je retiens juste ceci : quand on a :
a = <expression littérale d'un objet>
b = <la même expression littérale que dans la ligne précédente>
1. Ou bien l'expression littérale correspond à un objet mutable et dans ce cas
tu auras forcément a et b qui référencent des objets distincts dans la mémoire
(j'ai tenté d'expliquer ci-dessus pourquoi).
2. Ou bien l'expression littérale correspond à un objet immutable et là :
a. Ou bien Python arrive à être « malin » et alors a et b vont référencer le
même objet. Tant mieux. Rien dans ton code n'indique de lien entre a et b
et donc on souhaite qu'un changement d'état de l'objet référencé par a
n'ait aucune incidence sur l'objet référencé par b. Mais ceci ne se produira
jamais vu que l'objet référencé par a (et par b) est non modifiable. La
seule chose que tu pourras faire, c'est faire en sorte a et b référencent
un nouvel objet et là ce sera une autre histoire. Donc Python peut faire
cette optimisation mémoire dans ce cas là sans incidence logique sur le code.
b. Ou bien il n'est pas assez « malin » et alors a et b vont référencer deux objets
distincts dans la mémoire qui sont pourtant chacun immutables et rigoureusement
identiques. Une optimisation de la mémoire loupé, tant pis...
Qu'on soit dans le cas 2a ou 2b, c'est un détail d'implémentation pour toi le programmeur.
Rien n'oblige Python à être dans le cas 2b (où il est malin) à chaque fois. Pour toi le
programmeur, ça ne changera rien dans le comportement de ton code.
Après, si tu (toi le programmeur) veux absolument que a et b référence le même objet,
charge à toi de le dire _explicitement_ à Python avec ce code là :
a = <expression littérale d'un objet, immuable ou pas, on s'en fiche ici>
b = a
Là, tu auras la garantie d'avoir « (a is b) == True ». Finalement, on reste bien dans
la philosophie Python du « explicit is better than implicit ». ;)
Par contre, ce que je ne comprends pas c'est la chose suivante :
Situation 2 :
a = 500
b = 500
a is b





False
Lorsque je fais a = 500, si l'objet 500 n'existe pas, il est créé puis référencé par a. Ensuite en faisant b = 500, l'objet 500 existant déjà (il a, au pire, été créé en faisant a = 500), l'objet 500 ne devrait donc pas être créé et b devrait référencer là encore le même objet 500 déjà existant. Du coup a is b devrait renvoyer True mais ce n'est pas le cas.
Où mon raisonnement est-il faux ?

Je pense que tu as tous les éléments dans ce que j'ai dit ci-dessus.
Et, histoire de mettre encore un peu plus la pagaille, voici ce que j'observe
sur Python 3.6.9 issu de ma distribution Ubuntu 18.04.
Déjà, je constate comme toi, et je constate que ça commence à partir de l'entier 257 :
#--------------------------------
a = 255
b = 255
a is b



True
a = 256
b = 256
a is b



True
a = 257
b = 257
a is b



False
#--------------------------------
Maintenant, regarde ce truc rigolo :
#--------------------------------
a = 257; b = 257; a is b



True
a = 257
b = 257
a is b



False
#--------------------------------
Si je mets les instructions sur une seule ligne, j'ai bien « (a is b) == True ».
Tu vois bien qu'on est vraiment dans du détail d'implémentation.
Et, pour enfoncer le clou, j'ai un comportement différent quand c'est exécuté dans un script :
#--------------------------------
~$ cat /tmp/test.py
#!/usr/bin/env python3
a = 500
b = 500
x = (a is b)
print(x)
~$ /tmp/test.py
True
#--------------------------------
Encore une fois, tout ceci n'est pas si important. Ce qu'il est important de comprendre
ce que rien n'oblige Python à faire en sorte que deux variables a et b référencent le
même objet sauf si tu lui demandes explicitement avec quelque chose comme :
a = ...
b = a
Sans cette demande explicite de ta part, Python pourra être amené, éventuellement, à faire
en sorte que a et b référencent chacun le même objet, mais à la condition nécessaire (et
non suffisante) que celui-ci soit immutable et encore il ne fera pas à chaque fois (quand
est-ce qu'il le fera relève du détail d'implémentation).
Voilà. J'espère que c'est un peu plus clair pour toi.
À+
--
François Lafont
thomas
Le #26546037
Tout d'abord merci beaucoup François d'avoir pris le temps de formuler
une réponse aussi claire.
Le 10/05/2020 à 13:43, Francois Lafont a écrit :
Hello,
On 5/10/20 11:39 AM, thomas wrote:
J'ai une question à propose du comportement de Python sur la façon de
créer des objets et de les référencer.
Situation 1 :
 >>> a = 5
 >>> b = 5
 >>> a is b
True
Jusque-là je me dis que lorsque je fais a = 5, comme c'est un objet
qui doit déjà exister (j'imagine que c'est un objet assez courant,
utilisé par Python, qui est créé à son lancement et qu'il doit même
être déjà référencé), il n'est pas créé donc a va référencer l'objet 5
déjà existant. Ensuite quand je fais b = 5, il se passe la même chose
et b va référencer le même objet 5 déjà existant. Du coup, a et b
référencent le même objet, ce qui explique que a is b renvoie True.

Perso, de ce petit test, je pense qu'on ne peut pas affirmer que l'objet
« 5 »
existait avant même l'instruction « a = 5 » (même si c'est peut-être
tout à fait vrai.

En fait, avant de faire mes essais j'avais fait un getrefcount (du
module sys) sur l'entier 5 au démarrage de Python et j'avais obtenu 47.
On peut juste dire qu'effectivement, au moment de « b = 5 »,
Python a
fait pointer b vers l'objet « 5 » référencé par la variable a et on peut
penser que c'est dans un souci d'optimisation de la mémoire.

Quand j'ai fait mon test au départ, en faisant a = 5 puis b = 5, j'étais
sûr que a is b renverrait False puisque rien n'obligeait Python à faire
référencer cet entier 5 par a et par b. Effectivement, il y a peut-être
un soucis d'optimisation de la mémoire mais dans quel contexte se
produit-il ? Pour les entiers de petite taille ? Jusqu'à quelle taille ?
Et les autres types ? Cela se produit-il toujours ?
Et, au passage, cette
référence du même objet entre a et b est possible car l'objet référencé
« 5 »
est immutable. Cela ne pourrait pas se produire avec par exemple :
    a = []
    b = []
    a is b # => renverra forcément False.
vu que [] n'est pas un objet immutable, il est mutable. En effet, rien
dans le
code n'indique explicitement que a et b référencent le même objet. Donc
il n'y
a aucune raison pour qu'une modification de l'objet référencé par a modifie
l'état de l'objet référencé par b. Donc a et b sont nécessairement deux
objets
distincts de la mémoire.

Nous sommes entièrement d'accord dans ce cas-là.
Mais tout ceci relève du détail d'implémentation à mon avis et il ne
faut pas
trop s'embêter avec cela. Personnellement, je retiens juste ceci : quand
on a :
    a = <expression littérale d'un objet>
    b = <la même expression littérale que dans la ligne précédente>
1. Ou bien l'expression littérale correspond à un objet mutable et dans
ce cas
   tu auras forcément a et b qui référencent des objets distincts dans
la mémoire
   (j'ai tenté d'expliquer ci-dessus pourquoi).

Bien sûr.
2. Ou bien l'expression littérale correspond à un objet immutable et là :
    a. Ou bien Python arrive à être « malin » et alors a et b vont
référencer le
       même objet. Tant mieux. Rien dans ton code n'indique de lien
entre a et b
       et donc on souhaite qu'un changement d'état de l'objet référencé
par a
       n'ait aucune incidence sur l'objet référencé par b. Mais ceci ne
se produira
       jamais vu que l'objet référencé par a (et par b) est non
modifiable. La
       seule chose que tu pourras faire, c'est faire en sorte a et b
référencent
       un nouvel objet et là ce sera une autre histoire. Donc Python
peut faire
       cette optimisation mémoire dans ce cas là sans incidence logique
sur le code.

Nous sommes d'accord.
    b. Ou bien il n'est pas assez « malin » et alors a et b vont
référencer deux objets
       distincts dans la mémoire qui sont pourtant chacun immutables et
rigoureusement
       identiques. Une optimisation de la mémoire loupé, tant pis...
Qu'on soit dans le cas 2a ou 2b, c'est un détail d'implémentation pour
toi le programmeur.
Rien n'oblige Python à être dans le cas 2b (où il est malin) à chaque
fois.

Oui d'autant que pour optimiser la création d'objet immutable (ne pas
créer deux fois le même en l'occurrence), il faudrait à chaque fois que
Python regarde tous les objets immutables dont il dispose en mémoire et
cela demande du temps de recherche. Il y a concurrence, ce que l'on
gagne d'un côté on le perd de l'autre (pas dans le cas présent mais sur
des programmes)
Pour toi le programmeur, ça ne changera rien dans le comportement de ton code.
Après, si tu (toi le programmeur) veux absolument que a et b référence
le même objet,
charge à toi de le dire _explicitement_ à Python avec ce code là :
    a = <expression littérale d'un objet, immuable ou pas, on s'en
fiche ici>
    b = a

Bien sûr.
Là, tu auras la garantie d'avoir « (a is b) == True ». Finalement, on
reste bien dans
la philosophie Python du « explicit is better than implicit ». ;)
Par contre, ce que je ne comprends pas c'est la chose suivante :
Situation 2 :
 >>> a = 500
 >>> b = 500
 >>> a is b
False
Lorsque je fais a = 500, si l'objet 500 n'existe pas, il est créé puis
référencé par a. Ensuite en faisant b = 500, l'objet 500 existant déjà
(il a, au pire, été créé en faisant a = 500), l'objet 500 ne devrait
donc pas être créé et b devrait référencer là encore le même objet 500
déjà existant. Du coup a is b devrait renvoyer True mais ce n'est pas
le cas.
Où mon raisonnement est-il faux ?

Je pense que tu as tous les éléments dans ce que j'ai dit ci-dessus.
Et, histoire de mettre encore un peu plus la pagaille, voici ce que
j'observe
sur Python 3.6.9 issu de ma distribution Ubuntu 18.04.

Au passage, je ne l'ai pas dit (désolé) mais je suis sous W10 avec
Python 3.7.6
Déjà, je constate comme toi, et je constate que ça commence à partir de
l'entier 257 :
#--------------------------------
a = 255
b = 255
a is b



True
a = 256
b = 256
a is b



True
a = 257
b = 257
a is b



False
#--------------------------------

Très intéressant. En fait, avant de poster mon message, j'avais dans
l'idée de faire cette recherche pour voir à partir de quel entier
"l'optimisation" ne se fait plus. Au début, j'avais pris 10000 à la
place de 500. J'avais ensuite réduit à 1000 puis à 500 mais je n'étais
pas allé plus loin car je me disais que de toutes façons, cela ne
répondrait pas à ma question.
Maintenant, regarde ce truc rigolo :
#--------------------------------
a = 257; b = 257; a is b



True
a = 257
b = 257
a is b



False
#--------------------------------
Si je mets les instructions sur une seule ligne, j'ai bien « (a is b) ==
True ».
Tu vois bien qu'on est vraiment dans du détail d'implémentation.
Et, pour enfoncer le clou, j'ai un comportement différent quand c'est
exécuté dans un script :
#--------------------------------
~$ cat /tmp/test.py
#!/usr/bin/env python3
a = 500
b = 500
x = (a is b)
print(x)
~$ /tmp/test.py
True
#--------------------------------

Très intéressant ! Merci d'avoir fait ces tests
Encore une fois, tout ceci n'est pas si important. Ce qu'il est
important de comprendre
ce que rien n'oblige Python à faire en sorte que deux variables a et b
référencent le
même objet sauf si tu lui demandes explicitement avec quelque chose comme :
    a = ...
    b = a
Sans cette demande explicite de ta part, Python pourra être amené,
éventuellement, à faire
en sorte que a et b référencent chacun le même objet, mais à la
condition nécessaire (et
non suffisante) que celui-ci soit immutable et encore il ne fera pas à
chaque fois (quand
est-ce qu'il le fera relève du détail d'implémentation).
Voilà. J'espère que c'est un peu plus clair pour toi.
À+

Encore merci.
Damien Wyart
Le #26546112
J'ai une question à propose du comportement de Python sur la façon de
créer des objets et de les référencer.
Situation 1 :
a = 5
b = 5





a is b





True
[...]

Bonjour,
Voir par exemple ce lien pour une explication détaillée :
https://stackoverflow.com/questions/306313/is-operator-behaves-unexpectedly-with-integers
--
DW
thomas
Le #26546137
Merci pour ce lien. On observe donc un tel comportement puisque
l'implémentation de Python est ainsi faite pour les entiers de -5 à 256 :
"The current implementation keeps an array of integer objects for all
integers between -5 and 256, when you create an int in that range you
actually just get back a reference to the existing object."
Le 11/05/2020 à 08:43, Damien Wyart a écrit :
J'ai une question à propose du comportement de Python sur la façon de
créer des objets et de les référencer.
Situation 1 :
a = 5
b = 5



a is b



True
[...]

Bonjour,
Voir par exemple ce lien pour une explication détaillée :
https://stackoverflow.com/questions/306313/is-operator-behaves-unexpectedly-with-integers
Julien Salort
Le #26546151
Le 10/05/2020 à 13:43, Francois Lafont a écrit :
Mais tout ceci relève du détail d'implémentation à mon avis et il ne
faut pas
trop s'embêter avec cela. Personnellement, je retiens juste ceci : quand
on a :

D'ailleurs, le caractère "détail d'implémentation" est particulièrement
visible si on compare CPython avec Pypy ou Jython.
% python3
Python 3.7.7 (default, Mar 17 2020, 21:58:45)
[Clang 10.0.0 (clang-1000.10.44.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
a = 500
b = 500
a is b



False
% pypy3
Python 3.6.9 (?, Apr 16 2020, 19:31:08)
[PyPy 7.3.1 with GCC 4.2.1 Compatible Apple LLVM 9.1.0
(clang-902.0.39.2)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
a = 500
b = 500
a is b




True
% jython
Jython 2.7.2 (v2.7.2:925a3cc3b49d, Mar 21 2020, 10:03:58)
[Java HotSpot(TM) 64-Bit Server VM (Oracle Corporation)] on java1.8.0_101
Type "help", "copyright", "credits" or "license" for more information.
a = 500
b = 500
a is b



True
Julien
Publicité
Poster une réponse
Anonyme