Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Petites questions théoriques : peut-on comparer une variable liste avec un pointeur en C?

26 réponses
Avatar
Francois
Bonjour à tous,

J'ai quelques questions qui peut-être auront un rapport avec
l'implémentation, mais cela m'intéresse quand même.


1) Quand on fait

a = 2
b = a

Les deux variables a et b sont deux variables différentes, la zone
mémoire allouée pour a et celle pour b sont distinctes. Une modification
de a n'affectera pas b etc. Chacun vit ça vie.

Ce qui me chagrine, c'est que id(a) et id(b) me donne exactement la même
valeur, alors que, pour moi, a et b sont distinctes. D'ailleurs quand je
modifie b (via b = 50 par exemple), là, ce petit coquin de Python ne me
donne plus le même id (et heureusement). Avez vous une explication ?


2) Pour les listes, c'est différent et c'est bien dit dans les docs. Par
exemple :

l1 = [0,1,2,3,4]
l2 = l1

J'ai bien compris que "le nom l2 est un alias du nom l1" qui désigne le
même objet dans la mémoire de l'ordinateur. Une modification via l1 se
retrouvera en appelant la liste via l2. Par exemple :

l1[0] = 100

entraînera l2 de la forme [100,1,2,3,4]

Je voulais trouver une petite explication à cela. J'aimerais avoir votre
avis. Je me suis dit que, peut-être, l1 et l2 étaient des sortes de
pointeurs (comme en C) sur le premier élément de la liste.


3) J'ai lu ceci "avec a = 3, a est une référence, c'est-à-dire un nom
désignant l'emplacement mémoire d'une valeur (objet)". Cela veut-il dire
qu'en fait les variables dans Python sont toutes "des pointeurs en C" ?



--
François

10 réponses

1 2 3
Avatar
bruno.desthuilliers
On 8 avr, 21:34, Francois wrote:
Bonjour à tous,

J'ai quelques questions qui peut-être auront un rapport avec
l'implémentation, mais cela m'intéresse quand même.

1) Quand on fait

a = 2
b = a

Les deux variables a et b sont deux variables différentes,


a et b sont deux noms différents. A ce stade, ils sont associés à une
référence sur le même objet.

la zone
mémoire allouée pour a et celle pour b sont distinctes.


Si tu commences à raisonner avec les concepts du C, tu vas te faire du
mal pour rien. En Python, une variable n'est pas une étiquette sur une
adresse mémoire, c'est un nom associé à une référence sur un objet . Le
nom lui-même n'est rien d'autre que ça : un nom. Une clé dans une
table de hachage, si tu préfère (et accessoirement, hormis pour les
variables locales d'une fonction, c'est exactement ça : une clé dans
une table de hachage). Quant à la zone mémoire où réside l'objet (le s
zones mémoires, d'ailleurs, vu que c'est un type de donnée structuré
composé en partie de références sur d'autres objets...), c'est
l'affaire de la machine virtuelle. De ton point de vue, il pourrait
aussi bien être sur une tablette d'argile.

Donc,en bref, oublie les zones mémoires, et pense en termes d'objets.
Et dans ton cas, non, a et b référencent le *même* objet. En Python,
une assignation signifie "associe ce nom avec une référence sur cet
objet (ie : a = 2) ou sur l'objet référéncé par ce nom (ie: b = a)".
Un objet n'est *jamais* copié si tu ne le demande pas très
explicitement (en utilisant copy.copy() ou copy.deepcopy()).

Une modification
de a


Pour quelle définition de "modification" ? changement de l'état de
l'objet référencé par a, ou rebinding (réassignation) d'un autre obj et
sur a ?

Dans le premier cas, vu que 2 est un objet immutable, tu ne peux tout
simplement pas le modifier. Avec un objet mutable, par contre, toute
modification de l'objet sera 'visible' depuis tous les noms
référençant cet objet (et pour cause...). Par exemple:

c = [1]
d = c
c[0] = 2
print d
d.append(42)
print c

Dans le second cas (réassignation), faire pointer un nom sur un autre
objet est bien sûr sans incidence sur les autres paires nom:référence
pointant sur l'objet d'origine, ie:

c = [1]
d = c
assert id(c) == id(d)
# equivalent:
assert c is d

c = 'toto'
assert d is not c


n'affectera pas b etc. Chacun vit ça vie.


cf ci-dessus.

Ce qui me chagrine, c'est que id(a) et id(b) me donne exactement la même
valeur,


C'est normal.

alors que, pour moi, a et b sont distinctes.


les noms sont distinct. Mais ils pointent sur le même objet. C'est
équivalent à:

ns = {}
ns['a'] = 2
ns['b'] = ns['a']

Là, tu t'attends bien à ce que ns['a'] et ns['b']
"soient" (référencent) le même objet. Après, si tu fais:

ns['b'] = 50

tu t'attends aussi à ce que ns['b'] pointe sur un objet différent.

Si ton code est au top-level de ton script ou module (ou de
l'interpréteur), tu peux s/ns/globals()/g

D'ailleurs quand je
modifie b (via b = 50 par exemple)


Tu ne modifie pas 'b', tu le fait pointer sur un autre objet. Ce qui
est sans conséquence sur l'objet précédemment référencé par b (s i ce
n'est de décrémemter son compteur de références).

, là, ce petit coquin de Python ne me
donne plus le même id (et heureusement). Avez vous une explication ?


cf ci-dessus.

2) Pour les listes, c'est différent


Absolument pas. Ni pour aucune autre sorte d'objet.

et c'est bien dit dans les docs. Par
exemple :

l1 = [0,1,2,3,4]
l2 = l1

J'ai bien compris que "le nom l2 est un alias du nom l1" qui désigne le
même objet dans la mémoire de l'ordinateur. Une modification via l1 se
retrouvera en appelant la liste via l2. Par exemple :

l1[0] = 100

entraînera l2 de la forme [100,1,2,3,4]


Il y a une nette différence entre :
l1[0] = 100
et
1l = 100

Le premier est en fait du sucre syntaxique pour
l1.__setitem__(0, 100)

Bref, un appel de méthode qui va modifier l'état de la liste
référencée par l1.

Je voulais trouver une petite explication à cela. J'aimerais avoir votre
avis. Je me suis dit que, peut-être, l1 et l2 étaient des sortes de
pointeurs


une référence

(comme en C)


en l'occurence, comme en C++ ou en Java

sur le premier élément de la liste.


sur l'objet liste.


En Python, tu n'a *que* des références sur des objets, *jamais* de
types 'immédiat' comme en C. Ne laisse pas la syntaxe te tromper, ce
n'est pas parce que tu définis un objet via une expression littérale
que tu a autre chose qu'une référence sur un objet.

3) J'ai lu ceci "avec a = 3, a est une référence, c'est-à-dire un nom
désignant l'emplacement mémoire d'une valeur (objet)".


Bien qu'à peu près correcte (au moins pour CPython), cette explication
n'est AMHA pas très bonne en ce qu'elle fait appel à une notion
(l'emplacement mémoire) qui n'existes tout simplement pas dans le
langage (même si elle existe, bien sûr, dans l'implémentation du
langage...).

Cela veut-il dire
qu'en fait les variables dans Python sont toutes "des pointeurs en C" ?


Non, même pas. Une variable C de type pointeur contient une adresse
mémoire. Un nom ne contient rien - une clé dans une table de hachage
ne contient rien.

Avatar
Francois
Déjà, grand merci pour toutes tes explications.

Si tu commences à raisonner avec les concepts du C, tu vas te faire du
mal pour rien. En Python, une variable n'est pas une étiquette sur une
adresse mémoire, c'est un nom associé à une référence sur un objet. Le
nom lui-même n'est rien d'autre que ça : un nom. Une clé dans une
table de hachage, si tu préfère (et accessoirement, hormis pour les
variables locales d'une fonction, c'est exactement ça : une clé dans
une table de hachage).


Ah oui, tiens, et les variables locales d'une fonctions ? C'est aussi un
nom associé à une référence sauf que l'espace des noms dans lequel elle
se trouve est dans un espace temporaire disjoint de l'espace de nom du
script principal ?

Quant à la zone mémoire où réside l'objet (les
zones mémoires, d'ailleurs, vu que c'est un type de donnée structuré
composé en partie de références sur d'autres objets...), c'est
l'affaire de la machine virtuelle. De ton point de vue, il pourrait
aussi bien être sur une tablette d'argile.


Je comprends cette logique : il faut rester dans le cadre du langage
abstrait et ne pas rentrer dans l'implémentation. Tu as raison. Mais,
dès fois, je trouve que rentrer (un tout petit peu) dans
l'implémentation, ça m'aide à comprendre.


Donc,en bref, oublie les zones mémoires, et pense en termes d'objets.
Et dans ton cas, non, a et b référencent le *même* objet. En Python,
une assignation signifie "associe ce nom avec une référence sur cet
objet (ie : a = 2) ou sur l'objet référéncé par ce nom (ie: b = a)".
Un objet n'est *jamais* copié si tu ne le demande pas très
explicitement (en utilisant copy.copy() ou copy.deepcopy()).


Là, je crois que *tout* est dit dans ce paragraphe très clair. Je résume
pour voir.

1) En *C*, une variable est un objet (un peu au sens de Python, avec un
type [taille + façon d'être interprété], une adresse et une valeur). On
peut donc parler de l'adresse d'une variable. Deux variables avec des
noms différents ont chacune une adresse différente.

2) Avec *Python*, une variable est un simple nom faisant référence à un
objet (avec un type [taille + façon d'être interprété], une adresse et
une valeur). Une variable n'a pas d'adresse. Deux variables avec des
noms différents peuvent très bien référencer un même objet.

Par exemple avec ceci (en Python)

a=1
b=1
c=1

finalement, dans la mémoire se trouve un seul objet créé (l'entier 1).
Alors qu'avec l'équivalent en C, trois objets seraient crées (de même
valeur, mais à trois endroits différents).

Si j'ai bon, je crois que j'ai franchi un petit cap dans ma
compréhension de Python. :-)

Remarque en passant : Je me doutais bien que d'un langage à un autre,
des mêmes notions pouvaient avoir une implémentation différente. Mais
quand même, je n'imaginais pas que cela serait le cas pour la basique
notion de variable par exemple ! J'espère que d'un langage à un autre,
il ne faut pas tout réapprendre à chaque fois (car là, finalement, j'ai
du réapprendre ce qu'est une variable :-) ) ?


Tu ne modifie pas 'b', tu le fait pointer sur un autre objet. Ce qui
est sans conséquence sur l'objet précédemment référencé par b (si ce
n'est de décrémemter son compteur de références).


D'accord. Dans mes autres messages, j'ai du écrire cette erreur 20 fois.
Je crois que c'est compris. Ce qui m'intéresse ici, c'est la notion de
compteur de référence. Chaque objet possède un compteur de référence,
c'est-à-dire, si je comprends bien, un compteur qui donne le nombre de
variable qui font référence à cet objet. C'est ça ?

Heu ..., mais ça sert à quoi ? À effacer l'objet de la mémoire quand le
compteur est à 0 peut-être ?


Il y a une nette différence entre :
l1[0] = 100
et
l1 = 100

Le premier est en fait du sucre syntaxique pour
l1.__setitem__(0, 100)

Bref, un appel de méthode qui va modifier l'état de la liste
référencée par l1.


Ok, on garde une certaine cohérence sur le tout objet : avec [], on
applique sans le savoir une méthode à l'objet l1. Tu ne l'as pas précisé
mais avec l1 = 100, évidemment, on réassigne l1 vers un nouvel objet qui
n'est plus une liste.

Je voulais trouver une petite explication à cela. J'aimerais avoir votre
avis. Je me suis dit que, peut-être, l1 et l2 étaient des sortes de
pointeurs


une référence

(comme en C)


en l'occurence, comme en C++ ou en Java

sur le premier élément de la liste.


sur l'objet liste.


Ok, ok. Allez, quand même, en interne de chez interne (dans le code C de
CPython par exemple), il n'y a pas de pointeur qui traîne quelque part
dans la définition d'une variable Python ? Hein, pour me faire plaisir. :-)


En Python, tu n'a *que* des références sur des objets, *jamais* de
types 'immédiat' comme en C. Ne laisse pas la syntaxe te tromper, ce
n'est pas parce que tu définis un objet via une expression littérale
que tu a autre chose qu'une référence sur un objet.


Heu, un type immédiat c'est quoi ?


Cela veut-il dire
qu'en fait les variables dans Python sont toutes "des pointeurs en C" ?


Non, même pas. Une variable C de type pointeur contient une adresse
mémoire. Un nom ne contient rien - une clé dans une table de hachage
ne contient rien.


Allez, il n'y a pas un petit pointeur caché quelque part ...


--
François


Avatar
Francois
Il y a une nette différence entre :
l1[0] = 100
et
l1 = 100

Le premier est en fait du sucre syntaxique pour
l1.__setitem__(0, 100)

Bref, un appel de méthode qui va modifier l'état de la liste
référencée par l1.


Ok, on garde une certaine cohérence sur le tout objet : avec [], on
applique sans le savoir une méthode à l'objet l1. Tu ne l'as pas précisé
mais avec l1 = 100, évidemment, on réassigne l1 vers un nouvel objet qui
n'est plus une liste.


Plus j'y pense, plus je me dis que la syntaxe l1[0]0 est vraiment
traître. Elle fait vraiment penser à une réassignation comme l1 = 100,
alors qu'en fait c'est une méthode appliquée à un objet ("mutable") pour
le modifier (évidemment la syntaxe l1[0]0 est quand même
incontournable du point de vue pratique). C'est ça qui m'a induit en
erreur :

a = 2
b = a # a et b font référence au même objet
b = 5 # c'est une assignation de b vers un nouvel objet
# ce n'est pas une modification de l'objet référencé
# par b qui est non mutable
print a # donne 2
print b # donne 5 car b ne se réfère plus au même objet que a


a = [0,1,2,3]
b = a # a et b font référence au même objet
b[0] = 7 # ce n'est pas une assignation
# c'est une modification de l'objet (mutable)
# référencé par b, via une méthode
print a # donne [7,1,2,3] l'objet référencé par a et b est le
# même et il a subi une modification en cours de route
print b # donne [7,1,2,3]


Une réassignation ne modifie pas un objet, mais créé une référence vers
un nouvel objet. On peut modifier un objet, à condition qu'il soit
mutable, mais cela ne peut pas passer par une réassignation. Par une
méthode par exemple. Et b[0]=7, n'est pas une réassignation mais une
méthode cachée (la bougresse).

--
François


Avatar
Francois
b[0] = 7 # ce n'est pas une assignation


Bon, là j'exagère un peu. b[0]=7 est (je pense) une assignation du
premier élément de l'objet séquence référencé par a (ou par b), mais ce
n'est pas une modification de l'objet 0 (ancien objet référencé par
b[0]) mais plutôt une création d'une référence de b[0] vers le nouvel
objet 7. Cette réassignation, de fait, entraîne une modification de
l'objet séquence référencé par b et aussi par a.


--
François qui commence à ce mélanger les pinceaux et qui ferait mieux
d'aller se coucher. :-)

Avatar
bruno.desthuilliers
On 9 avr, 01:12, Francois wrote:
Déjà, grand merci pour toutes tes explications.


Si tu commences à raisonner avec les concepts du C, tu vas te faire du
mal pour rien. En Python, une variable n'est pas une étiquette sur une
adresse mémoire, c'est un nom associé à une référence sur un o bjet. Le
nom lui-même n'est rien d'autre que ça : un nom. Une clé dans une
table de hachage, si tu préfère (et accessoirement, hormis pour les
variables locales d'une fonction, c'est exactement ça : une clé dans
une table de hachage).


Ah oui, tiens, et les variables locales d'une fonctions ? C'est aussi un
nom associé à une référence sauf que l'espace des noms dans lequel elle
se trouve est dans un espace temporaire disjoint de l'espace de nom du
script principal ?


Exactement. Note bien que ce sont les *noms* qui sont locaux, pas les
objets qu'ils référencent. C'est particulièrement important à
comprendre pour les paramètres de fonction: si tu modifie (mutation)
un objet passé en paramètre, bien que le nom par lequel tu accèdes à
l'objet soit local, l'objet, lui, est bien celui qui a été passé à l a
fonction. Par contre, si tu rebinde un paramètre, ça n'affecte que
l'espace de nommage local. Exemple:


def unefonc(liste, dico):
liste.append(42)
dico = 'allo'

uneliste = [1, 2, 3]
undico = dict(a=1, b=2, c=3)

print uneliste, undico
unefonc(uneliste, undico)
print uneliste, undico

Quant à la zone mémoire où réside l'objet (les
zones mémoires, d'ailleurs, vu que c'est un type de donnée structur é
composé en partie de références sur d'autres objets...), c'est
l'affaire de la machine virtuelle. De ton point de vue, il pourrait
aussi bien être sur une tablette d'argile.


Je comprends cette logique : il faut rester dans le cadre du langage
abstrait et ne pas rentrer dans l'implémentation. Tu as raison. Mais,
dès fois, je trouve que rentrer (un tout petit peu) dans
l'implémentation, ça m'aide à comprendre.


Tout à fait d'accord. "dès fois", et "un petit peu". En sachant que
d'autres implémentations sont possibles - au moins théoriquement, mais
parfois aussi pratiquement. Le problème pour toi, ici, c'est de
commencer par faire abstraction (justement) de ce que tu a appris pour
le C.

Donc,en bref, oublie les zones mémoires, et pense en termes d'objets.
Et dans ton cas, non, a et b référencent le *même* objet. En Pytho n,
une assignation signifie "associe ce nom avec une référence sur cet
objet (ie : a = 2) ou sur l'objet référéncé par ce nom (ie: b = a)".
Un objet n'est *jamais* copié si tu ne le demande pas très
explicitement (en utilisant copy.copy() ou copy.deepcopy()).


Là, je crois que *tout* est dit dans ce paragraphe très clair. Je ré sume
pour voir.

1) En *C*, une variable est un objet (un peu au sens de Python, avec un
type [taille + façon d'être interprété], une adresse et une valeur ). On
peut donc parler de l'adresse d'une variable. Deux variables avec des
noms différents ont chacune une adresse différente.


admettons. minus le "un peu au sens de Python".

2) Avec *Python*, une variable est un simple nom faisant référence à un
objet (avec un type [taille + façon d'être interprété],


La notion de type est totalement différente.

Selon les catégories du C, il n'existe qu'un seul type en Python, le
type Py_Object_ptr, qui est un pointeur sur une structure de données
(je parle bien sûr de l'implémentation CPython - en Jython ou
IronPython, c'est probablement différent) (et bien sûr, je simplifie
outrageusement).

Selon la sémantique propre à Python, le 'type' d'un objet, c'est
l'objet class (int, dict, function, etc) qu'il référence via son
attribut __class__. A ce niveau, il n'y a pas de notion de 'taille',
de 'façon d'être interprété' ou autre. Il y a juste une référenc e sur
un autre objet dans lequel l'interpréteur ira chercher les attributs
non trouvés dans l'objet lui-même.

une adresse et
une valeur). Une variable n'a pas d'adresse. Deux variables avec des
noms différents peuvent très bien référencer un même objet.


Oui.

Par exemple avec ceci (en Python)

a=1
b=1
c=1

finalement, dans la mémoire se trouve un seul objet créé (l'entier 1 ).


Avec CPython, oui, mais parce que les petits entiers sont conservé en
cache. Si tu remplace 1 par 33333, tu aura trois objets différents.
Par contre, si tu fais:

a = 33333
b = a
c = b

Tu aura bien trois noms référençant un seul et même objet.


Alors qu'avec l'équivalent en C, trois objets seraient crées (de mêm e
valeur, mais à trois endroits différents).


Oui.

Si j'ai bon, je crois que j'ai franchi un petit cap dans ma
compréhension de Python. :-)


Y a du progrès.

Remarque en passant : Je me doutais bien que d'un langage à un autre,
des mêmes notions pouvaient avoir une implémentation différente. Mai s
quand même, je n'imaginais pas que cela serait le cas pour la basique
notion de variable par exemple !


Attends de voir un langage fonctionnel pur (comme Haskell par
exemple) : tu ne peux tout simplement pas modifier la valeur d'une
variable une fois qu'elle est crée !-)

J'espère que d'un langage à un autre,
il ne faut pas tout réapprendre à chaque fois (car là, finalement, j 'ai
du réapprendre ce qu'est une variable :-) ) ?


Non, la sémantique des variables de Python (de ce point de vue là) se
retrouve, à l'identique ou de façon très proche, dans pas mal d'autres
langages de haut niveau. De même que la sémantique des variables C se
retrouve dans pas mal de langages de bas niveau.

Tu ne modifie pas 'b', tu le fait pointer sur un autre objet. Ce qui
est sans conséquence sur l'objet précédemment référencé par b (si ce
n'est de décrémemter son compteur de références).


D'accord. Dans mes autres messages, j'ai du écrire cette erreur 20 fois.
Je crois que c'est compris. Ce qui m'intéresse ici, c'est la notion de
compteur de référence. Chaque objet possède un compteur de référ ence,
c'est-à-dire, si je comprends bien, un compteur qui donne le nombre de
variable qui font référence à cet objet. C'est ça ?


pile-poil.

Heu ..., mais ça sert à quoi ? À effacer l'objet de la mémoire qua nd le
compteur est à 0 peut-être ?


bingo !-)

Il y a une nette différence entre :
l1[0] = 100
et
l1 = 100

Le premier est en fait du sucre syntaxique pour
l1.__setitem__(0, 100)

Bref, un appel de méthode qui va modifier l'état de la liste
référencée par l1.


Ok, on garde une certaine cohérence sur le tout objet : avec [], on
applique sans le savoir


moi, je le sais !-)

En pratique, à part l'assignation à un nom simple (ie: sans '.' ni []
dans l'affaire), toutes les opérations de Python sont en fait des
appels de méthode, et toutes sont surchargeables.

une méthode à l'objet l1. Tu ne l'as pas précisé
mais avec l1 = 100, évidemment, on réassigne l1 vers un nouvel objet qui
n'est plus une liste.


Indeed.

Je voulais trouver une petite explication à cela. J'aimerais avoir vo tre
avis. Je me suis dit que, peut-être, l1 et l2 étaient des sortes de
pointeurs


une référence

(comme en C)


en l'occurence, comme en C++ ou en Java

sur le premier élément de la liste.


sur l'objet liste.


Ok, ok. Allez, quand même, en interne de chez interne (dans le code C de
CPython par exemple), il n'y a pas de pointeur qui traîne quelque part
dans la définition d'une variable Python ? Hein, pour me faire plaisir. :-)


Si, bien sûr. cf ci-dessus pour l'implémentation du type objet dans
CPython. A vrai dire, y a même tellement de pointeurs que tu ne sais
plus où donner de la tête. Content ?-)

En Python, tu n'a *que* des références sur des objets, *jamais* de
types 'immédiat' comme en C. Ne laisse pas la syntaxe te tromper, ce
n'est pas parce que tu définis un objet via une expression littérale
que tu a autre chose qu'une référence sur un objet.


Heu, un type immédiat c'est quoi ?


un entier en C.

Cela veut-il dire
qu'en fait les variables dans Python sont toutes "des pointeurs en C" ?


Non, même pas. Une variable C de type pointeur contient une adresse
mémoire. Un nom ne contient rien - une clé dans une table de hachag e
ne contient rien.


Allez, il n'y a pas un petit pointeur caché quelque part ...


Tu y tiens, hein ?-)

Mais bon, il y a d'autres implémentations de Python, dans des langages
qui ne supportent pas les pointeurs (Jython et IronPython). Ce qui
démontre bien que la notion de pointeur n'est pas nécessaire ni pour
implémenter Python, ni pour le comprendre. (NB : bon, ok, Java (et
probablement .NET) est aussi implémenté en C, donc on finis *toujours*
par avoir des bits à des adresses mémoires...)



Avatar
Amaury Forgeot d'Arc
b[0] = 7 # ce n'est pas une assignation


Bon, là j'exagère un peu. b[0]=7 est (je pense) une assignation du
premier élément de l'objet séquence référencé par a (ou par b), mais ce
n'est pas une modification de l'objet 0 (ancien objet référencé par
b[0]) mais plutôt une création d'une référence de b[0] vers le nouvel
objet 7. Cette réassignation, de fait, entraîne une modification de
l'objet séquence référencé par b et aussi par a.


Il y en a au moins un qui se couchera plus savant cette nuit.

--
Amaury


Avatar
bruno.desthuilliers
On 9 avr, 02:29, Francois wrote:
(snip)
Plus j'y pense, plus je me dis que la syntaxe l1[0]0 est vraiment
traître. Elle fait vraiment penser à une réassignation comme l1 = 100,
alors qu'en fait c'est une méthode appliquée à un objet ("mutable") pour
le modifier (évidemment la syntaxe l1[0]0 est quand même
incontournable du point de vue pratique). C'est ça qui m'a induit en
erreur :


T'inquiètes pas, t'es pas le premier (et c'est pas non plus la
première fois que j'explique ça).

a = 2
b = a # a et b font référence au même objet
b = 5 # c'est une assignation de b vers un nouvel objet
# ce n'est pas une modification de l'objet référencé
# par b qui est non mutable
print a # donne 2
print b # donne 5 car b ne se réfère plus au même objet que a

a = [0,1,2,3]
b = a # a et b font référence au même objet
b[0] = 7 # ce n'est pas une assignation
# c'est une modification de l'objet (mutable)
# référencé par b, via une méthode
print a # donne [7,1,2,3] l'objet référencé par a et b est le
# même et il a subi une modification en cours de route
print b # donne [7,1,2,3]

Une réassignation ne modifie pas un objet, mais créé une référen ce vers
un nouvel objet. On peut modifier un objet, à condition qu'il soit
mutable, mais cela ne peut pas passer par une réassignation. Par une
méthode par exemple.


Par une méthode uniquement. Même si l'appel de méthode n'est pas
explicite.

Et b[0]=7, n'est pas une réassignation mais une
méthode cachée (la bougresse).


Certes, mais elle est tellement plus élégante comme ça qu'on lui
pardonne !-)

Bon, je crois que t'es bon, là.

Avatar
bruno.desthuilliers
On 9 avr, 03:08, Amaury Forgeot d'Arc wrote:

b[0] = 7 # ce n'est pas une assignation


Bon, là j'exagère un peu. b[0]=7 est (je pense) une assignation du
premier élément de l'objet séquence référencé par a (ou par b),



Dans la cas d'une liste, oui, on en arrive finalement à ça. Mais je
pourrais écrire une classe telle que la même syntaxe n'effectue aucune
assignation.

mais ce
n'est pas une modification de l'objet 0 (ancien objet référencé pa r
b[0]) mais plutôt une création d'une référence de b[0] vers le n ouvel
objet 7.



Je ne sais pas pourquoi, mais cette formulation me semble ambigüe...

Cette réassignation, de fait, entraîne une modification de
l'objet séquence référencé par b et aussi par a.



Tout à fait. Mais ne modifie pas l'espace de nommage en cours, et
n'affecte pas les liaisons entre a et b et l'objet séquence (précision
à l'adresse de l'OP).


Il y en a au moins un qui se couchera plus savant cette nuit.


A ce propos, je ne sais pas si je suis plus savant, mais il serait
temps que j'y aille...



Avatar
Francois
On 9 avr, 02:29, Francois wrote:
(snip)
Plus j'y pense, plus je me dis que la syntaxe l1[0]0 est vraiment
traître. Elle fait vraiment penser à une réassignation comme l1 = 100,
alors qu'en fait c'est une méthode appliquée à un objet ("mutable") pour
le modifier (évidemment la syntaxe l1[0]0 est quand même
incontournable du point de vue pratique). C'est ça qui m'a induit en
erreur :


T'inquiètes pas, t'es pas le premier (et c'est pas non plus la
première fois que j'explique ça).


Ah, désolé. Faut dire, à ma décharge, que tout cela n'est pas vraiment
bien expliqué dans le peu de livres que j'ai.


Une réassignation ne modifie pas un objet, mais créé une référence vers
un nouvel objet. On peut modifier un objet, à condition qu'il soit
mutable, mais cela ne peut pas passer par une réassignation. Par une
méthode par exemple.


Par une méthode uniquement. Même si l'appel de méthode n'est pas
explicite.


Par une méthode *uniquement*, Ok. C'est précis et simple. Ça me va !


Et b[0]=7, n'est pas une réassignation mais une
méthode cachée (la bougresse).


Certes, mais elle est tellement plus élégante comme ça qu'on lui
pardonne !-)


Oui, demain matin tout cela sera pardonné. Oops, on est déjà demain
matin. :-)


Bon, je crois que t'es bon, là.


Moins mal qu'il y a quelques heures en tout cas. Merci encore.


--
François


Avatar
Francois
On 9 avr, 01:12, Francois wrote:
Ah oui, tiens, et les variables locales d'une fonctions ? C'est aussi un
nom associé à une référence sauf que l'espace des noms dans lequel elle
se trouve est dans un espace temporaire disjoint de l'espace de nom du
script principal ?


Exactement. Note bien que ce sont les *noms* qui sont locaux, pas les
objets qu'ils référencent. C'est particulièrement important à
comprendre pour les paramètres de fonction: si tu modifie (mutation)
un objet passé en paramètre, bien que le nom par lequel tu accèdes à
l'objet soit local, l'objet, lui, est bien celui qui a été passé à la
fonction.


Ok, là, ça fait aussi une sacrée différence avec le C.

Par contre, si tu rebinde un paramètre, ça n'affecte que
l'espace de nommage local. Exemple :


Et là, c'est un peu comme le C, mais pour des raisons différentes. Ok,
pour l'exemple. Merci, c'est très clair.



2) Avec *Python*, une variable est un simple nom faisant référence à un
objet (avec un type [taille + façon d'être interprété],


La notion de type est totalement différente.

Selon les catégories du C, il n'existe qu'un seul type en Python, le
type Py_Object_ptr, qui est un pointeur sur une structure de données
(je parle bien sûr de l'implémentation CPython - en Jython ou
IronPython, c'est probablement différent) (et bien sûr, je simplifie
outrageusement).

Selon la sémantique propre à Python, le 'type' d'un objet, c'est
l'objet class (int, dict, function, etc) qu'il référence via son
attribut __class__. A ce niveau, il n'y a pas de notion de 'taille',
de 'façon d'être interprété' ou autre. Il y a juste une référence sur
un autre objet dans lequel l'interpréteur ira chercher les attributs
non trouvés dans l'objet lui-même.


D'accord, ça c'est d'un point de vue du langage abstrait. Tu
m'accorderas bien qu'un objet aura, in fine, une taille, une adresse et
une manière d'être interprété par la machine virtuelle Python. Non ?


Par exemple avec ceci (en Python)

a=1
b=1
c=1

finalement, dans la mémoire se trouve un seul objet créé (l'entier 1).


Avec CPython, oui, mais parce que les petits entiers sont conservé en
cache.


Heu, c'est quoi le cache ? Pour moi, c'est une sorte de mémoire RAM qui
est seulement plus rapide et moins volumineuse que la RAM classique.
Est-ce juste ?
Si oui, en quoi cela justifie que 1 se trouve en un seul endroit de la
mémoire ?

Si tu remplace 1 par 33333, tu aura trois objets différents.


Et zut. Ça me chagrine un peu ça. En effet :

a = 333333
b = 333333
id(a)
135973460



id(b)
135973424




et donc la comparaison "id(a) == id(b)" n'est pas logiquement
équivalente à la comparaison "a==b". C'est dommage, non ?

Je pensais que si Python tombait sur une affectation d'un objet déjà
existant, il ne le récréait pas, mais faisait une référence à celui déjà
existant, cela dans le but de prendre moins de place dans la mémoire.
Pourquoi diable adopter cette stratégie quand on a des «petits» entiers,
et ne pas l'adopter pour des «gros» entiers ??? Je ne saisis pas bien la
logique là dedans ?


Par contre, si tu fais:

a = 33333
b = a
c = b

Tu aura bien trois noms référençant un seul et même objet.


Ouf !


Remarque en passant : Je me doutais bien que d'un langage à un autre,
des mêmes notions pouvaient avoir une implémentation différente. Mais
quand même, je n'imaginais pas que cela serait le cas pour la basique
notion de variable par exemple !


Attends de voir un langage fonctionnel pur (comme Haskell par
exemple) : tu ne peux tout simplement pas modifier la valeur d'une
variable une fois qu'elle est crée !-)


On laissera ça pour ... une autre vie :-)


J'espère que d'un langage à un autre,
il ne faut pas tout réapprendre à chaque fois (car là, finalement, j'ai
du réapprendre ce qu'est une variable :-) ) ?


Non, la sémantique des variables de Python (de ce point de vue là) se
retrouve, à l'identique ou de façon très proche, dans pas mal d'autres
langages de haut niveau. De même que la sémantique des variables C se
retrouve dans pas mal de langages de bas niveau.


Ouf !


Ok, ok. Allez, quand même, en interne de chez interne (dans le code C de
CPython par exemple), il n'y a pas de pointeur qui traîne quelque part
dans la définition d'une variable Python ? Hein, pour me faire plaisir. :-)


Si, bien sûr. cf ci-dessus pour l'implémentation du type objet dans
CPython. A vrai dire, y a même tellement de pointeurs que tu ne sais
plus où donner de la tête. Content ?-)


Oui ! :-)


En Python, tu n'a *que* des références sur des objets, *jamais* de
types 'immédiat' comme en C. Ne laisse pas la syntaxe te tromper, ce
n'est pas parce que tu définis un objet via une expression littérale
que tu a autre chose qu'une référence sur un objet.
Heu, un type immédiat c'est quoi ?



un entier en C.


Mais s'il y a *que* des références à des objets, cela veut dire que le
simple entier "1" en Python est un réalité un objet plus complexe qu'un
simple int ou char en C. Son codage binaire par exemple sera plus
complexe que le naturel "00000001" ?


--
François



1 2 3