TLDR : Python noms fonctionnent comme des pointeurs avec dé/référencement automatique mais ne permettent pas d'opérations explicites sur les pointeurs. D'autres cibles représentent des indirections, qui se comportent comme des pointeurs.
La spécification du langage Python ne définit pas ce que sont les noms et autres éléments de ce type sont seulement comment ils se comportent. Toutefois, le comportement peut être expliqué à l'aide de pointeurs.
L'implémentation de CPython utilise pointeurs de type PyObject*
sous le capot. En tant que tel, il est possible de traduire la sémantique des noms en opérations sur les pointeurs. La clé est de séparer noms de la réalité objets .
L'exemple de code Python inclut les deux noms ( i
) et des objets ( 5
).
i = 5 # name `i` refers to object `5`
j = i # ???
j = 3 # name `j` refers to object `3`
Cela peut être traduit grossièrement en code C avec séparé les noms et les objets.
int three=3, five=5; // objects
int *i, *j; // names
i = &five; // name `i` refers to position of object `5`
j = i; // name `j` refers to referent of `i`
j = &three; // name `j` refers to position of object `3`
L'important est que les "noms en tant que pointeurs" ne stockent pas d'objets ! Nous n'avons pas défini *i = five
mais i = &five
. Les noms et les objets existent indépendamment les uns des autres.
Noms seulement pointer vers les objets existants en mémoire.
Lors d'une affectation de nom à nom, aucun objet n'est échangé ! Lorsque nous définissons j = i
ce qui équivaut à j = &five
. Ni l'un ni l'autre i
ni j
sont connectés les uns aux autres.
+- name i -+ -\
\
--> + <five> -+
/ | 5 |
+- name j -+ -/ +----------+
En conséquence, le changement de la cible d'un nom n'affecte pas l'autre . Il ne met à jour que ce vers quoi pointe ce nom spécifique.
Python possède également d'autres types d'éléments ressemblant à des noms : références d'attributs ( i.j
), les abonnements ( i[j]
) et le découpage en tranches ( i[:j]
). Contrairement aux noms, qui se réfèrent directement aux objets, les trois font indirectement référence à des éléments de objets.
L'exemple de code inclut les deux noms ( i
) et un abonnement ( i[0]
).
i = [1,2,3] # name `i` refers to object `[1, 2, 3]`
j = i # name `j` refers to referent of `i`
i[0] = 5 # ???
Un CPython list
utilise un tableau C de PyObject*
sous le capot. Cela peut à nouveau être traduit grossièrement en code C avec des noms et des objets séparés.
typedef struct{
int *elements[3];
} list; // length 3 `list` type
int one = 1, two = 2, three = 3, five = 5;
list values = {&one, &two, &three}; // objects
list *i, *j; // names
i = &values; // name `i` refers to object `[1, 2, 3]`
j = i; // name `j` refers to referent of `i`
i->elements[0] = &five; // leading element of `i` refers to object `5`
L'important, c'est que nous n'avons changé aucun nom ! Nous avons changé i->elements[0]
l'élément d'un objet que nos deux noms désignent.
Les valeurs des objets composés existants peuvent être modifiées.
Lors de la modification de la valeur d'un objet par le biais de un nom, les noms ne sont pas modifiés. Les deux sites i
y j
font toujours référence au même objet, dont nous pouvons modifier la valeur.
+- name i -+ -\
\
--> + <values> -+
/ | elements | --> [1, 2, 3]
+- name j -+ -/ +-----------+
L'objet intermédiaire se comporte comme suit : est similaire à un pointeur en ce sens que nous pouvons modifier directement ce vers quoi il pointe et le référencer à partir de plusieurs noms.