point = [0, 0, 0]
x, y, z = point[0], point[1], point[2]
Il faut garder le modèle mental de correspondance :
0 pour l'abscisse x,
1 pour l'ordonnée y,
2 pour la profondeur z.
Cette correspondance complexifie le code.
Limites des conteneurs (2/2)
Modélisation d'un point 3D avec un dictionnaire :
point = {"x": 0, "y": 0, "z": 0}
x, y, z = point["x"], point["y"], point["z"]
Cette solution implique une empreinte mémoire supérieure.
Structure de données
Python nous permet de définir nos propres structures de données.
from dataclasses import dataclass
@dataclassclassPoint:
x: float = 0.
y: float = 0.
z: float = 0.
point = Point()
x, y, z = point.x, point.y, point.z
Avantages
Plus explicite que l'emploi des indexes d'une liste.
Code plus précis et plus simple à comprendre et maintenir.
Moins lourd en mémoire par rapport à un dictionnaire.
Egalement plus performant qu'une liste car il n'y a pas d'indirection.
Conclusion
Les structures de données sont fondamentales pour définir des abstractions de niveau supérieur et simplifier la programmation.
Toute opération non destructive est une lecture.
Cette impossibilité est une caractéristique intéressante.
Elle est voulue.
Nous allons y revenir très prochainement.
On ne peut pas vraiment forcer la suppression d'un objet en Python.
Il s'agit d'un langage dit "managé".
Cela signifie que la fin du cycle de vie d'un objet est géré par l'interpréteur.
En pratique, il existe un processus particulier nommé garbage collector (collecteur de déchets) dont le rôle est de libérer la mémoire des objets qui ne sont pas utilisés.
Une fois que la chaine est mise à None, les caractères ne sont plus référencés, et la mémoire associée sera donc libérée par le garbage collector à son prochain passage.
La chaîne "oui" reste inchangée.
Cette chaîne n'est tout simplement plus référencée par la variable chaine.
La syntaxe (1) est déjà réservée pour le nombre entier 1 entre paranthèses.
Il ne peut pas y avoir 2 sémantiques associées à la même syntaxe dans le même contexte.
Donc, il fallait une syntaxe différente. Cette syntaxe consiste à avoir une virgule supplémentaire à la fin.
La fonction range renvoie un objet range.
Cet objet n'est pas un conteneur.
Il ne renvoie donc pas directement les valeurs 0 à 9 dans cet exemple.
Dans le second cas, les mêmes valeurs sont renvoyées par les ranges.
0, 2, 4, 6, 8
Il n'a pas été nécessaire de construire un ensemble d'un milliard d'éléments.
Par conséquent, l'exécution est instantannée.
Simplement en rajoutant le mot clé tuple, il faut maintenant 1 minute pour exécuter ce code sur une station de travail avec un CPU i9 à 4.5GHz.
L'ajout de ce mot clé force l'interpréteur à créer un tuple contenant 1 milliard d'éléments, même si on ne regarde que les 300 premiers.
On parle ici d'évaluation gourmande (greedy evaluation), qui est l'inverse de l'évaluation paresseuse.
Le fait de modifier la 2e liste modifie également la 1ière.
Est-ce surprenant ?
Pas vraiment, la variable liste1 est liée à la liste [1, 2, 3].
Ensuite, on lie la variable liste2 à la même liste [1, 2, 3].
Donc, les variables liste1 et liste2 référencent la même liste.
Il s'agit exactement du même cas que dans la diapositive précédente.
Même si le passage des variables se fait par valeur en Python, l'argument liste se voit lié à la même liste que la variable liste1.
Par conséquent, la modification de la liste dans la fonction impacte la variable liste1.
Parfois, c'est ce que l'on veut, parfois, non.
Deux listes sont égales si elles possèdent le même nombre d'éléments et si chacun d'entre eux sont égaux 2 à 2.
Cela n'aide pas à savoir si les 2 variables liste1 et liste2 référencent la même liste.
L'opérateur is permet de déterminer avec certitudes sur 2 variables référencent le même objet.
On sait ainsi si la modification de l'une des variables va entrainer la modification de l'autre.
Dans ce cas, le clonage nous a permi de créer très facilement un clone indépendant de l'original.
Donc maintenant, lorsque l'on modifie la variable liste2, liste1 reste inchangée.
Il s'agit de la même technique. Seule la syntaxe change.
Il est important de comprendre cette syntaxe car elle est souvent utilisée.
On utilise l'opérateur de slicing pour renvoyer une nouvelle liste.
Cette nouvelle liste va du début à la fin de la liste actuelle, car on utilise les valeurs par défaut.
En effet, lorsque l'on ne spécifie pas la valeur avant le ":", c'est 0.
De même, lorsque l'on ne spécifie pas la valeur après le ":", c'est len(liste).
Un clonage effectue une copie uniquement au premier niveau.
Par conséquent, les sous-listes de liste1 sont toujours référencées dans liste2.
La diapositive suivante clarifie les étapes.
Etapes de clonage
La copie profonde permet de dupliquer l'ensemble de la structure, quelque soit sa profondeur.
Ainsi, on a maintenant liste1 et liste2 qui sont liés à des structures totalement différentes.
Le set n'est lui-même pas hashable.
Il n'est donc pas possible d'utiliser un set de set.
Comme le set n'est pas ordonné, on ne peut pas accéder au ième élément.
Par conséquent, l'opérateur [] n'est pas défini.
Attention, si vous devez utiliser une version de Python inférieure strictement à 3.8, le comportement sera différent.
Les dictionnaires ont été ré-implémentés dans Python 3.8, et la nouvelle implémentation permet de garantir la conservation de l'ordre.
Cela dit, vous ne devriez pas vous baser sur cette propriété de telle sorte que votre code fonctionne quelque soit la version de l'interpréteur utilisé
Les compréhensions sont faites pour rendre le code plus simple, et non plus complexe.
Ici, la liste interne est créée avec un range(3) donc comportera 3 élément.
La compréhension externe est composée de 2 boucles.
La première boucle, sur j est limitée aux éléments 0 et 3 car ce sont les seuls éléments dont le reste de la division par 3 est égal à 0 sur l'intervalle [0 ; 6[.
La deuxième boucle, sur k, est limitée aux éléments 0 et 1.
La liste externe est donc composée de 2x2 = 4 listes internes.
Les 2 premières listes internes sont obtenues pour j == 0 et respectivement k == 0 et k == 1.
Les 2 listes internes suivantes sont obtenues pour j == 3 et respectivement k == 0 et k == 1.
Avec les conteneurs vus jusqu'ici, on peut aller loin.
Nous allons voir une alternative puissante avec les structures de données
Les éléments d'une liste sont dispersés en mémoire.
Pour accéder à la localisation mémoire d'un élément d'une liste, une indirection est nécessaire en pratique.
Les éléments d'une structure de données sont compacts en mémoire