pour visualiser le comportement de nos programmes, et notamment cet aspect de partage de la mémoire
nous allons utiliser des illustrations produites par l’excellent https://
%load_ext ipythontutorcomposition des types de base¶
tous ces containers peuvent être imbriqués
liste de dictionnaires de …donc ils peuvent être composés sans limite
uniquement votre faculté à vous y retrouver
(enfin, vous et ceux qui vous lisent …)
%%ipythontutor heapPrimitives=true curInstr=1 width=900 height=850
# une liste avec une sous-liste qui contient un dict et un tuple
L = ['abc', [ { (1, 2) : 1}, ([3], 4)], 5]typage dynamique¶
si on exécute
a = 3Python va
créer un objet représentant 3
créer une variable
asi elle n’existe pas encore
(une entrée dans une table)faire de
aune référence de l’objet (ou vers l’objet, si vous préférez)
%%ipythontutor heapPrimitives=true width=800 curInstr=1
a = 3références partagées¶
du coup on peut facilement avoir plusieurs variables qui référencent le même objet
%%ipythontutor heapPrimitives=true width=800 curInstr=2
# on aurait pu écrire aussi
# a = b = 3
a = 3
b = arappel : mutable vs immutable¶
les entiers, les chaines, les tuples sont immutables
ils ne peuvent pas être modifiés
il n’y a pas d’effet de bord possible sur un objet immutable
il est impossible qu’une modification sur
baffectea
références partagées vers objet immutable¶
%%ipythontutor heapPrimitives=true width=800 curInstr=1
a = 3
b = a
b += 1idem avec objet mutable¶
on prend de nouveau deux références vers le même objet
mais que se passe-t-il si l’objet est mutable ? eh bien il peut être changé
et alors cela impacte toutes les références vers cet objet
que ce soit depuis une variable, ou depuis l’intérieur d’un autre objet
%%ipythontutor heapPrimitives=true width=800 height=300 curInstr=1
a = [1, 2]
b = a
# en changeant a on change b
a[0] = 'spam'types mutables / immutables¶
| type | mutable ? |
|---|---|
int et autres nombres | non |
str | non |
list | mutable |
tuple | non |
dict | mutable |
set | mutable |
frozenset | non |
shallow et deep copies¶
la solution pour ne pas modifier b: faire une copie de a
il y a deux types de copies en Python:
la shallow copy (superficielle)
pour les toutes les séquences, utiliser simplement
a[:]pour les dictionnaires utiliser
D.copy()sinon, utiliser le module
copy.copy()
la deep copy (profonde)
copy.deepcopy()pour tout copier de manière récursive
différence¶
la différence entre shallow et deep copy n’existe que pour les
objets composites (les objets qui contiennent d’autres objets)shallow copy: crée un nouvel objet composite et y insère
les références vers les objets contenus dans l’originaldeep copy: crée un nouvel objet et insère de manière récursive
une copie des objets trouvés dans l’original
évite les boucles infinies
exemple d’utilisation de shallow copy¶
%%ipythontutor heapPrimitives=true height=400 width=800 curInstr=1
# comme ci-dessus
a = [1, 2]
# mais cette fois-ci on (shallow) copie d'abord
b = a[:]
# b n'est pas modifié
a[0] = 'spam'illustration shallow¶
illustration deep¶
rappel: is et ==¶
obj1 is obj2ssi obj1 et obj2 sont le même objet
forme inverse:
obj1 is not obj2
obj1 == obj2ssi les valeurs des objets sont égales
forme inverse
obj1 != obj2
a = [0, 1, 2]
b = a[:]
a is bFalsea == bTruec = d = [0, 1, 2]
c is dTruec == aTruecopie profonde nécessaire ?¶
voyons maintenant un cas où la donnée de départ est -un peu - plus profonde, maintenant ça fait une différence de choisir l’une ou l’autre copie
%%ipythontutor heapPrimitives=true height=450 width=800 curInstr=1
# cette fois a est un peu plus profond
a = [1, [2]]
# si on ne fait qu'une copie 'shallow'
b = a[:]
# on peut toujours modifier b à travers a
a[1][0] = 'boom'
print(a)
print(b)%%ipythontutor heapPrimitives=true height=500 width=900 curInstr=2
import copy
a = [1, [2]]
# maintenant avec une copie profonde: on n'a plus de souci
b = copy.deepcopy(a)
a[1][0] = 'boom'
print(a)
print(b)autres types de références partagées (avancé)¶
on n’a parlé jusqu’ici que de références partagées créées par affectation
mais il existe (plein) d’autres cas de figure :
appel de fonction: l’objet passé en argument à une fonction
se retrouve affecté au paramètre dans la fonctionse méfier aussi des références entre objets
appel de fonction¶
%%ipythontutor
L = [1, 2, 3]
def foo(x):
x[1] = 'BOOM'
foo(L)
print(L)références entre objets¶
toutes les parties d’objet qui sont atteignables à partir d’autres objets peuvent devenir des références partagées
pas besoin qu’il y ait nécessairement plusieurs variables dans le paysage
on peut le voir sur l’exemple pathologique suivant
%%ipythontutor heapPrimitives=true height=400 width=900 curInstr=1
repete = 4 * [[0]]
print(f"repete avant {repete}")
repete[0][0] = 1
print(f"repete après {repete}")structures cycliques¶
ainsi on peut aussi créer des structures cycliques
%%ipythontutor
L = [None]
L[0] = L
print(L)gestion de la mémoire (avancé)¶
Python sait réutiliser les objets
e.g. les petits entiers - slide suivantPython sait libérer les objets non utilisés (garbage collector)
un objet sans référence est libéré
contrôle via le module
gc
chaque objet contient deux champs dans l’entête
un champ désignant le typage - cf
type(obj)un champ contenant un compteur de références
voirsys.getrefcount(obj)
optimisation interne à Python¶
# avec cette forme
# on crée deux objets liste
L = [1, 2]
M = [1, 2] # nouvel objet liste
L is MFalse# ici aussi on pourrait penser
# créer deux objets int
I = 18
J = 18 # nouvel objet entier ?
I is J # non: partage
# (optimisation)Trueest-ce que ça pose un problème ?
non ! l’optimisation n’est que pour des types immutables