Du Python, des objets, des identifiants, et des petites subtilités…
Disclaimer
Cet article a été initialement écrit en anglais (conformément à la consigne) mais le blog étant en francais, je me suis permis de le traduire ci dessous :
Introduction
Dans le cadre de ma reconversion en informatique, je dois apprendre à coder, mais aussi à communiquer avec d’autres personnes et, en particulier, à rédiger de la documentation technique. Dans cette optique, nous devons rédiger un article de vulgarisation au sujet de la programmation orientée objet et de Python. En effet, en Python, tout est objet et il est important de comprendre certains principes…
ID et Type
Un petit rappel néanmoins : pour bien comprendre ce qui va suivre, il faut bien comprendre ce qu’est un objet. Un objet est une entité en mémoire qui contient des données. Chaque objet a :
- un type : c’est-à-dire sa classe d’origine (
int
,str
,list
,MyCustomObject
, etc.) - un identifiant : Un identifiant unique qui est associé à l’adresse mémoire utilisée. Que nous appellerons
id
par la suite. - une valeur : la donnée associée.
En Python, il faut savoir que tout est un objet. Les int
, str
, list
, mais aussi les fonctions, les classes, etc…
Par conséquent, pour chaque objet, on peut savoir son id
ainsi que son type
avec les commandes id
et type
.
Exemple
x = 1242 # Déclaration d'une variable X print(x)# Affichage de la valeur 1242 print(id(x)) # Affichage de son identifiant print(type(x)) # Affichage de son type (<class 'int'>) y = 10.1010 # Déclaration d'une variable Y print(y) # Affichage de la valeur 10.1010 print(id(y)) # Affichage de son identifiant print(type(y)) # Affichage de son type (<class 'float'>) z = "Live long and prosper" # Déclaration d'une variable Z print(z) # Affichage de la valeur "Live long and prosper" print(id(z)) # Affichage de son identifiant print(type(z)) # Affichage de son type (<class 'str'>)
Pour aller plus loin, manipulons un peu le code :
a = 1337 b = a c = 1991 print("ID of a:", id(a)) print("ID of b:", id(b)) print("ID of c:", id(c))
Et voici un exemple d’output du code ci-dessus :
ID of a: 138767925128816 ID of b: 138767925128816 ID of c: 138767925128880
On peut voir que l’id
de a
et de b
sont identiques. Pourquoi ? Parce que tout est objet en Python et que la valeur de a
et la valeur de b
pointent vers la même adresse mémoire. Donc ils ont le même id
et la même valeur. Ce qui n’est pas le cas de c
qui possède une valeur différente.
Objets mutables
En Python, certains objets sont dits mutables, c’est-à-dire qu’on peut les modifier à loisir et cela va modifier la valeur de l’objet, sans modifier son adresse mémoire. En Python, les principaux objets mutables sont les suivants :
list
Exemple :[1, 2, 3]
dict
Exemple :{"a": 1, "b": 2}
set
Exemple :{1, 2, 3}
C’est-à-dire que si l’on peut, par exemple, ajouter des valeurs à une liste, sans que cela modifie son id
, c’est-à-dire l’adresse mémoire qui est associée.
Exemple :
mylist = [1,2,3] print("Value of mylist before update:", mylist) print("ID of mylist before update:", id(mylist)) mylist.append(4) print("Value of mylist after update:", mylist) print("ID of mylist after update:", id(mylist))
Output :
Value of mylist before update: [1, 2, 3] ID of mylist before update: 135825999858112 Value of mylist after update: [1, 2, 3, 4] ID of mylist after update: 135825999858112
Comme on peut le voir, même si les valeurs dans l’objet mylist
ont été modifiées, vu que c’est un objet mutable, on garde le même id
!
Objets immuables
On a vu qu’il existe des objets mutables. Par opposition, les objets immuables sont ceux qui ne peuvent pas être modifiés. Et les objets immuables sont tous ceux qui ne sont pas dans la liste des objets mutables que l’on vient de voir précédemment ; mais voici les principaux :
int
Exemple :x = 42
float
Exemple :x = 101.010
str
Exemple :x = "hello darkness my old friend"
bool
Exemple :x = True
tuples
Exemple :(1,2,3)
Il en existe d’autres moins utilisés comme, par exemple, frozenset
ou bytes
.
Exemple :
myint = 101010 print("Value of myint before update:", myint) print("ID of myint before update:", id(myint)) myint = myint + 1 print("Value of myint after update:", myint) print("ID of myint after update:", id(myint))
Output :
Value of myint before update: 101010 ID of myint before update: 135973928496752 Value of myint after update: 101011 ID of myint after update: 135973928496656
Comme on peut le voir, vu qu’on change la valeur de myint
, Python attribue une nouvelle valeur à myint
et doit donc lui donner une nouvelle adresse mémoire pour stocker cette nouvelle valeur, et par conséquent un nouvel id
! C’est pour cela que les id
ne sont plus les mêmes après la modification.
Comment les arguments sont passés aux fonctions
En Python, tout est objet, par conséquent quand l’on passe des arguments à une fonction, c’est en réalité la référence à cet objet qui est passée et non pas l’objet lui-même. Cela signifie que la fonction voit l’objet original et agit directement sur lui si c’est un objet mutable.
C’est-à-dire que si j’ai une variable myvar = 12
et que je dois utiliser cette variable dans une fonction, ce que reçoit la fonction est un lien vers l’objet 12 stocké en mémoire.
Avec des immuables
Les objets immuables ne peuvent pas être modifiés comme on l’a vu précédemment. Donc même en passant l’objet à la fonction, vu que l’argument est passé par référence, on ne change pas l’objet original.
Exemple :
def increment(x): x += 1 a = 5 increment(a) print(a) # Affiche 5 car l'objet original n'a pas été modifié
Ici, x
dans la fonction reçoit la référence à a
, mais comme les entiers sont immuables, l’assignation x += 1
crée un nouvel objet. a
reste inchangé en dehors de la fonction.
Avec les mutables
Les objets mutables peuvent être modifiés à la volée comme on l’a vu précédemment. Donc en passant l’objet à la fonction, on modifie l’objet original :
Exemple :
def modify(l): l.append(3) a = [1, 2] modify(a) print(a) # Affiche 1,2,3 car l'objet original a été modifié
Ici, l
dans la fonction reçoit la référence à a
, mais comme les listes sont mutables, l’assignation l.append(3)
modifie l’objet a
.
Pour aller plus loin
Certaines valeurs simples comme les petits entiers (de -5 à 256) sont pré-enregistrées en mémoire. Cela signifie que même si on affecte une variable avec l’une de ces valeurs, Python réutilise la même adresse mémoire. Cette optimisation est appelée interning.
Par exemple :
print("Exemple with int in range -5 / 255 (intering) ") x1 = 42 y1 = x1 + 0 print("ID of x:", id(x1)) print("ID of y:", id(y1)) print("Exemple with int > 256") x2 = 9999 y2 = x2 + 0 print("ID of x:", id(x2)) print("ID of y:", id(y2))
Output:
Exemple with int in range -5 / 255 (intering) ID of x: 133548043464040 ID of y: 133548043464040 Exemple with int > 256 ID of x: 133548031831600 ID of y: 133548031831568
Même si on déclare deux variables différentes, elles pointent vers le même objet en mémoire, car Python a décidé de ne pas en recréer un car c’est une valeur courante (entre -5 et 255).
Voila, c’est tout pour le moment !
Comments by OursBlanc