cerhu > comp.lang.* > comp.lang.python

thomas (10/05/2020, 11h39)
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
Francois Lafont (10/05/2020, 13h43)
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 :
> 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 :

#--------------------------------
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.
À+
thomas (10/05/2020, 16h05)
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:
>> 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 ?

[..]
> 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.

[..]
> 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 ». ;)
> 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 :
> #--------------------------------
> True
> True
> 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.

[..]
> #!/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

[..]
> 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 (11/05/2020, 08h43)
> 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 :
[..]
thomas (11/05/2020, 11h11)
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 :
[..]
Julien Salort (11/05/2020, 16h21)
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
Discussions similaires
Une explication?

une explication SVP

explication ?

PPM VRD Explication?


Fuseau horaire GMT +2. Il est actuellement 05h59. | Privacy Policy