# Types mutables, types persistants : quelles différences ? Quelles conséquences
Dans ce TP nous approfondissons les notions de types mutables et persistants.
:::info
:bulb: **Prérequis**: les opérations de base sur les types **list** et **tuple** doivent être maîtrisées : ajout, suppression, initialisation, affectation, copie, etc.
:::
## Différence entre type mutable et type persistant
Les types dits **mutable** en anglais (muable en français) sont modifiables. Ainsi, une liste, après sa déclaration peut subir un certain nombre de modifications et être mise à jour au fur et à mesure de l'évolution du programme dans lequel elle est utilisée.
**Exemple :**
```python=
maListe = [1,2,3,4]
maListe.append(5)
print(maListe)
maListe.pop()
print(maListe)
```
Les tuples (p-uplet) de leur côté ne peuvent être modifiés. Ils sont dits immuables ou persistants en français (immutable en anglais).
**1) Relevez le message d'erreur obtenu après exécution de ce script**
``` python=
monUplet = (1,2,3,4,5)
monUplet[0] = 8
monUplet.append(67)
monUplet.pop()
```
TypeError: 'tuple' object does not support item assignment. Ce qui signifie que le tuple ne peut changer de valeur, il ne peut être modifier, il n'est pas mutable.
**2) À votre avis, est-il pertinent d'utiliser des p-uplets pour stocker des notes d'élèves ?**
:::warning
mon_P_Uplet = (1,2,3,4,5)
mon_P_Uplet[4] = 4
mon_P_Uplet.append(12)
print(mon_P_Uplet)
:::
Pour stocker des notes d'éléves il faut pouvoir les changer ou bien les modifier voire même en ajouter.
La persistance ou la muabilité d'un type de données en Python possède plusieurs conséquences que nous allons explorer dans ce TP
## Conséquence 1 : l'utilisation des alias
### Pour les listes
La création d'une liste contenant les valeurs `["a", "b", "c"]` se fait en allouant un espace mémoire dans le tas.
Le script suivant génère l'allocation de deux espaces mémoire distincts bien que les deux variables possèdent les mêmes valeurs.
```python=
liste1= ["a", "b", "c"]
liste2 = ["a", "b", "c"]
```
**3) Si l'on modifie `liste1`, cela aura t-il des conséquences sur `liste2` ?**
:::warning
liste1= ["a", "b", "c"]
liste2 = ["a", "b", "c"]
liste1.append("d")
print(liste1)
print(liste2)
:::
Intéressons-nous maintenant au script suivant :
```python=
liste3= ["a", "b", "c"]
liste4 = liste3
```
**4) Modifier `liste3` à l'aide d'une opération de votre choix.**
```python=
liste3= ["a", "b", "c"]
liste4 = liste3
liste3.append("d")
print(liste3)
print(liste4)
```
**5) Affichez la valeur de `liste3` et de `liste4`. Que constatez-vous ?**
```python=
liste3= ["a", "b", "c"]
liste4 = liste3
liste3.append("d")
print(liste3)
print(liste4)
```
Nous constatons donc que lorsque l'on change la liste 3, la liste 4 change aussi. Cela est du au fait que la liste 4 a pour valeur la liste 3 (ligne 2)
**6) Quelle est la différence entre la `copie` (`liste5`) et la création d'`alias`(`liste4`)**
```python=
liste5 = liste3.copy()
```
```python=
liste1= ["a", "b", "c"]
liste2=["e","f","g"]
liste3=liste1 #alias
liste4 = liste1.copy() #copie
liste1[1] = 'z'
print(liste1)
print(liste2)
print(liste3)
print(liste4)
```
Nous remarquons que l'utilisation de copy permet de copier la première version de la liste (avant changements) tandis que alias recopie la dernière version de la liste.
**7) À partir des observations effectuées en questions 5 et 6, donnez une définition à la notion d'`alias`.**
Alias est donc une affectation d'une liste à une autre en prenant en compte les changements effectués.
### Pour les tuples
Vérifions maintenant
**8)Testez le code suivant. Que constatez-vous ?**
```python=
tuple1 = (1,2,3)
tuple2 = tuple1
tuple1 = tuple1 + (4,5)
print(tuple1)
print(tuple2)
```
Elle affiche : (1, 2, 3, 4, 5) et (1, 2, 3) nous remarquons donc que pour les tuples l'affectation ne prend pas en compte les changements contrairement aux listes.
:::success
L'instruction `tuple1 = tuple1 + (4,5)` est exécutée sans générer d'erreurs car elle ne modifie pas la valeur originelle de `tuple1`. Elle ne fait qu'affecter (à l'aide de l'opérateur d'affectation `=`) une nouvelle valeur à `tuple1` sans toucher à l'ancienne.
:::
**9)Quelles conclusions pouvez-vous en tirer ?**
Les affectations chez les listes et les tuples ne fonctionnent pas de la même façon.
## Conséquence 2 : l'utilisation de la copie
**10) Créer une fonction `copie_liste` qui prend en paramètre une liste et retourne sa copie**
**Exemple :**
```python=
liste1 = [1,2,3]
liste2 = copie_liste(liste1)
```
```python=
def copieListe(liste1):
liste2 = liste1.copy()
return liste2
print(copieListe([1,2,3,4]))
```
**11) Testons l'utilisation de la fonction précédente sur une matrice**
```python=
liste3 = [[1,2],[3,4]]
liste4 = copie_liste(liste3)
liste3[0][1] = 6
```
**12) Afficher `liste3` et `liste4`. Quelles sont les modifications observées ? Donnez une explication suite à ces observations.**
Renvoie : [[1, 6], [3, 4]] et [[1, 6], [3, 4]]
Sur une matrice, les modifications faites sur une première liste sont également appliquéessur ue autre liste avec la fonction 'copy'.
:::info
Et maintenant, un peu de récursivité :)
:::
**13) Créer une fonction récursive qui permet d'effectuer la copie d'une liste qu'il s'aggisse d'une liste simple, d'une matrice ou d'une liste de listes de listes**
```python=
def copie_liste_recursive(liste):
...
return copie
```
```python=
def copie_liste_recursive(t):
if isinstance(t, list):
res=[]
for x in t:
res.append( copie_liste_recursive(x))
return res
else:
return t
l1 = [[9,0],[6,7]]
l2 = [1,2,3,4,5]
l3 = [[[9,0],[6,7]], [[1,2,3,4,5],[1,2]]]
print(copie_liste_recursive(l1))
print(copie_liste_recursive(l2))
print(copie_liste_recursive(l3))
```
**14) Tester votre fonction récursive à l'aide des variables suivantes :**
```python=
l1 = [[9,0],[6,7]]
l2 = [1,2,3,4,5]
l3 = [[[9,0],[6,7]], [[1,2,3,4,5],[1,2]]]
```
renvoie :
[[9, 0], [6, 7]]
[1, 2, 3, 4, 5]
[[[9, 0], [6, 7]], [[1, 2, 3, 4, 5], [1, 2]]]
## Conséquence 3 : les paramètres de fonction mutables
### Gestion des impuretés
La création d'alias peut émerger dans différentes situations. Ainsi, lorsque l'on effectue des appels de fonctions à l'aide de variables globales (entre autres) des alias peuvent être générés.
**14) Testez le code suivant et comparez les valeurs des deux variables avant et après l'appel de fonction**
```python=
def fct_test(maListe):
maListe.append(1)
return
maListe = [1,2,3]
ex2 = [5]
fct_test(ex2)
print(maListe)
print(ex2)
```
renvoie :
[1, 2, 3]
[5, 1]
maListe ne change pas car l'appel de fonction n'entre en paramètre que la liste ex2 donc 1 lui est rajouté.
**15)En ce qui concerne le paramètre maListe de fct_test et la variable maListe, subissent-ils les mêmes modifications ? Donnez une explication.**
Le paramètre subit des modifications apportées par la fonction tandis que la variable ne change pas si elle n'est pas prise en paramètre.