Skip to article frontmatterSkip to article content

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://pythontutor.com

%load_ext ipythontutor

composition des types de base

%%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]
Loading...

typage dynamique

%%ipythontutor heapPrimitives=true width=800 curInstr=1
a = 3
Loading...

ré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 = a
Loading...

rappel : mutable vs immutable

les entiers, les chaines, les tuples sont immutables

références partagées vers objet immutable

%%ipythontutor heapPrimitives=true width=800 curInstr=1

a = 3
b = a
b += 1
Loading...

idem 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'
Loading...

types mutables / immutables

typemutable ?
int et autres nombresnon
strnon
listmutable
tuplenon
dictmutable
setmutable
frozensetnon

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:

différence

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'
Loading...

illustration shallow

illustration deep

rappel: is et ==

a = [0, 1, 2]
b = a[:]
a is b
False
a == b
True
c = d = [0, 1, 2]
c is d
True
c == a
True

copie 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)
Loading...
%%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)
Loading...

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

%%ipythontutor
L = [1, 2, 3]

def foo(x):
    x[1] = 'BOOM'

foo(L)
print(L)
Loading...

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}")
Loading...

structures cycliques

ainsi on peut aussi créer des structures cycliques

%%ipythontutor

L = [None]
L[0] = L
print(L)
Loading...

gestion de la mémoire (avancé)

optimisation interne à Python

# avec cette forme
# on crée deux objets liste
L = [1, 2]
M = [1, 2] # nouvel objet liste
L is M
False
# ici aussi on pourrait penser
# créer deux objets int
I = 18
J = 18   # nouvel objet entier ?
I is J   # non: partage
         # (optimisation)
True

est-ce que ça pose un problème ?
non ! l’optimisation n’est que pour des types immutables