494 lines
19 KiB
Markdown
494 lines
19 KiB
Markdown
---
|
||
title: Découvrir le langage - types de données avancés
|
||
author: Steve Kossouho
|
||
---
|
||
|
||
# Collections de données
|
||
|
||
----
|
||
|
||
## Les listes
|
||
|
||
Type de collection de données : `list`{.python}. Une liste peut contenir une **séquence** d'éléments
|
||
de n'importe quel type pris en charge par Python, y compris d'autres listes. Le contenu d'une liste est modifiable (ajouter, retirer des éléments…)
|
||
|
||
```python {.numberLines}
|
||
liste1 = [1, 2, 3, 4, 5, 6] # types cohérents
|
||
liste2 = [1, 2, 3, "a", "b", "c"] # types divers
|
||
liste3 = [None, None, True, False]
|
||
liste4 = [[1, 2, 3], [4, 5, 6]]
|
||
liste5 = [] # liste vide, équivalent à list()
|
||
liste6 = [liste1] # Liste contenant 1 élément, qui est lui-même une liste
|
||
```
|
||
|
||
----
|
||
|
||
Il est possible de manipuler une liste facilement. On peut ajouter des éléments à la fin, à une
|
||
position précise, retirer un élément ou encore récupérer un élément seul.
|
||
|
||
```python {.numberLines}
|
||
liste1 = [1, 2, 3, 4, 5, 6]
|
||
liste2 = list() # Créer une nouvelle liste vide
|
||
|
||
# Infos sur la liste
|
||
length = len(liste1) # Récupérer le nombre d'éléments
|
||
position = liste1.index(3) # Renvoie l'index de la valeur 3, ou erreur si introuvable
|
||
nine_trouvable = 9 in liste1 # Renvoie si l'élément 9 existe dans la liste
|
||
# Récupérer des éléments
|
||
print(liste1[0]) # Affiche le premier élément si la liste est non vide
|
||
# Manipuler le contenu
|
||
liste1.append(7) # Ajoute 7 comme nouvel élément à la fin
|
||
liste1.insert(0, 99) # Insère 99 comme nouvel élément au tout début
|
||
liste1[0] = 98 # Remplace la valeur à l'index 0
|
||
liste1.remove(4) # Enlève la première occurrence du nombre 4, ou erreur si introuvable
|
||
del liste1[3] # Retire l'élément à l'index 3
|
||
```
|
||
|
||
[Méthodes accessibles sur les listes](https://docs.python.org/3/tutorial/datastructures.html#more-on-lists)
|
||
|
||
----
|
||
|
||
### Erreurs d'accès
|
||
|
||
Accéder à un élément de liste n'existant pas génère une erreur (`IndexError`{.python}) et interrompt
|
||
votre programme; il peut être utile d'utiliser la fonction `len()`{.python} pour tester que vous
|
||
accédez à un indice valide.
|
||
|
||
```python {.numberLines}
|
||
liste1 = [1, 2, 3]
|
||
if len(liste1) >= 4:
|
||
print(liste1[3])
|
||
```
|
||
|
||
----
|
||
|
||
### Erreurs de méthodes
|
||
|
||
La méthode `.remove(valeur)`{.python} provoque également une erreur (`ValueError`{.python}) si
|
||
l'élément en argument n'existe pas dans la liste. De la même façon, il peut être utile de tester
|
||
qu'un élément est présent dans la liste avant d'essayer de l'en supprimer :
|
||
|
||
```python {.numberLines}
|
||
liste1 = [1, 2, 3]
|
||
if 8 in liste1:
|
||
liste1.remove(8)
|
||
```
|
||
|
||
----
|
||
|
||
### Indices de liste négatifs
|
||
|
||
Il est également possible d'accéder à des éléments de liste en utilisant des indices négatifs.
|
||
|
||
Si la liste n'est pas vide, l'élément d'indice `-1` est le dernier élément de la liste (équivalent
|
||
à `len(liste) - 1`{.python}), et l'élément à l'indice `-len(liste)`{.python} est le premier (
|
||
équivalent à `0`).
|
||
|
||
Tout nombre inférieur génère une erreur de type `IndexError`{.python}.
|
||
|
||
```python {.numberLines}
|
||
liste1 = [1, 2, 4, 6, 9, 11]
|
||
print(liste1[-1])
|
||
```
|
||
|
||
----
|
||
|
||
## Parcourir une liste
|
||
|
||
Comme on l'a vu avec la boucle `for`{.python}, utilisée avec `range()`{.python}, on peut utiliser la
|
||
boucle `for`{.python} sur une liste :
|
||
|
||
```python {.numberLines}
|
||
prime_numbers = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31]
|
||
for number in prime_numbers:
|
||
print(number)
|
||
```
|
||
|
||
La variable `number`{.python} déclarée pour la boucle contiendra tour à tour
|
||
les valeurs `2`{.python}, puis `3`{.python}, puis `5`{.python}, etc.
|
||
|
||
|
||
----
|
||
|
||
## Opérations sur listes
|
||
|
||
Récupérer des portions ou partitions de listes (slicing) :
|
||
|
||
```python {.numberLines}
|
||
a = [10, 15, 20, 25, 30, 35, 40, 45, 50, 55]
|
||
b = a[0:5] # Index 0 à 5 non inclus. Marche aussi sur les chaînes.
|
||
c = a[5:0] # L'index de fin est inférieur au départ, renvoie une liste vide
|
||
d = a[:] # Renvoie une copie de toute la liste
|
||
e = a[::-1] # Tout parcourir à l'envers
|
||
f = a[::2] # Tout parcourir 2 par 2
|
||
g = a[5:2:-1] # L'indice de début est supérieur à l'indice de fin
|
||
h = a[2:5:-1] # Le pas est négatif, start < end, renvoie une liste vide
|
||
```
|
||
|
||
Pourquoi créer des portions de listes ? Cela peut-être utile, par exemple, si
|
||
vous souhaitez appliquer un calcul sur une petite partition d'un jeu de données.
|
||
|
||
----
|
||
|
||
## Autres collections
|
||
|
||
Outre les listes, il existe 3 types de base pour collectionner des éléments :
|
||
|
||
- Tuple : `tuple()`{.python}
|
||
- Set (jeu d'éléments uniques) : `set()`{.python}
|
||
- Dictionnaires (association) : `dict()`{.python}
|
||
|
||
----
|
||
|
||
### Tuples
|
||
|
||
**Tuple** : Un `tuple`{.python} fonctionne quasiment trait pour trait comme une liste, à la différence qu'une
|
||
fois que vous avez défini ses éléments, vous ne pouvez plus en changer. On parle de « collection immuable ».
|
||
|
||
```python {.numberLines}
|
||
a = (1, 2, 3)
|
||
b = () # ou tuple()
|
||
c = (1,) # il vous faut au minimum une virgule
|
||
d = (None, "Salut", 15, 2.718)
|
||
```
|
||
|
||
Les méthodes de modification de contenu, telles que `append`{.python} etc. ne sont logiquement pas disponibles avec
|
||
ce type : [opérations des tuples](https://docs.python.org/3/library/stdtypes.html#common-sequence-operations)
|
||
|
||
----
|
||
|
||
### Ensembles (données uniques)
|
||
|
||
**Set** (ensemble) : Un `set`{.python} peut contenir plusieurs éléments et est modifiable au même titre
|
||
qu'une liste, mais n'est pas une séquence; aucune notion d'ordre ou d'index.
|
||
La collection garantit l'unicité des éléments, un élément ne pouvant apparaître qu'une fois au maximum
|
||
dans votre objet.
|
||
|
||
```python {.numberLines}
|
||
a = {1, 1, 2, 3, 3, 3} # équivaut à {1, 2, 3}, les valeurs étant uniques à la fin
|
||
b = {2, 4, 5, "hello", (1, 2, 3)}
|
||
c = set() # obligatoire pour déclarer un set vide, sinon considéré dict
|
||
print(len(b))
|
||
a.add(9.5) # ajoute la valeur 9.5 (float) au set
|
||
a.discard(9.5) # retire la valeur 9.5, ou ne fait rien
|
||
d = a.intersection(b) # renvoie un set avec les éléments communs à a et b
|
||
e = a.union(b) # avec les éléments présents dans a ou b
|
||
...
|
||
```
|
||
|
||
[Méthodes disponibles sur les ensembles](https://docs.python.org/fr/3/library/stdtypes.html#set)
|
||
|
||
----
|
||
|
||

|
||
|
||
----
|
||
|
||
#### Bonus : Particularités des ensembles
|
||
|
||
Les ensembles sont des structures de données très efficaces. Techniquement, vous pouvez attendre une
|
||
complexité en `O(1)` pour que Python sache si un élément est déjà présent ou pas; cela signifie qu'il
|
||
faut plus ou moins le même nombre d'opérations pour trouver un élément, que votre `set`{.python} en possède un
|
||
seul ou un milliard.
|
||
|
||
L'algorithme et la structure de données interne à Python derrière cette efficacité se nomme **Table de hachage**.
|
||
|
||
----
|
||
|
||
##### Tables de hachage
|
||
|
||
Une table de hachage fonctionne en trois temps :
|
||
|
||
- On réserve des zones de mémoire d'avance (**buckets**), au départ vides;
|
||
- lorsque l'on souhaite vérifier si un élément y est présent, on lui calcule une signature (`hash()`{.python});
|
||
- la signature de l'objet permet de savoir quelle zone de mémoire consulter pour le trouver.
|
||
- si la zone de mémoire est vide, c'est que l'objet n'était pas présent.
|
||
|
||
----
|
||
|
||
##### Exemple de hachage
|
||
|
||

|
||
|
||
----
|
||
|
||
##### Hachage
|
||
|
||
La signature est un nombre calculé qui est sensé dépendre du contenu d'un objet. Python propose une fonction
|
||
à cet effet, `hash()`{.python}, pour laquelle chaque classe propose sa propre implémentation.
|
||
L'objectif d'un hash est simple; deux objets différents doivent avoir un hash différent.
|
||
Dans Python ce dernier est un nombre entier sur 64 bits. Si deux objets a et b ont le même hash, ils seront généralement
|
||
considérés comme équivalents et donc en **collision** (dans ce cas, `a == b`{.python}). S'ils ne sont pas considérés équivalents,
|
||
il n'y a pas de collision.
|
||
|
||
----
|
||
|
||
##### Hachage en général dans Python
|
||
|
||
Entre deux lancements d'un interpréteur Python, le hash de la majorité des objets est différent;
|
||
l'algorithme utilise une valeur aléatoire pour générer les hashs, de façon qu'il soit impossible de créer un
|
||
dictionnaire de signatures dédié à créer des collisions.
|
||
|
||
Le hachage est en général imprévisible pour les valeurs suivantes :
|
||
|
||
- `str`{.python} non vides
|
||
- `float`{.python} non équivalents à un `int`{.python}
|
||
- `None`{.python}
|
||
- `tuple`{.python}
|
||
|
||
----
|
||
|
||
##### Hashes prévisibles
|
||
|
||
Le hash est par contre prévisible pour les valeurs suivantes :
|
||
|
||
- `0`{.python}, `0.0`{.python}, `False`{.python} et `""`{.python} ont un hash de `0`{.python} mais `""`{.python} n'est pas équivalent;
|
||
- `1`{.python}, `1.0`{.python} et `True`{.python} ont un hash de `1`{.python} et sont tous en collision.
|
||
- `int`{.python}, où le hash est identique au nombre...
|
||
|
||
Les objets `int`{.python} ont généralement un `hash()`{.python} identique, sauf cas suivants :
|
||
|
||
- `-1`{.python} a un hash de `-2`{.python} car la valeur `-1`{.python} est un code d'erreur;
|
||
- `2 ** 61 - 2`{.python} est le dernier nombre positif avec un hash identique;
|
||
- à partir de `2 ** 61 - 1`{.python}, le hash revient à 0 etc.
|
||
- le même algorithme fonction sur les entiers négatifs, mais avec des hashes négatifs.
|
||
|
||
----
|
||
|
||
##### Hashes impossibles
|
||
|
||
L'algorithme de la table de hachage nécessite qu'un objet stocké dans un bucket possède en
|
||
permanence un `hash` correspondant à son bucket. Cela pose un problème avec les objets modifiables
|
||
à tout moment, tels que les `list`{.python}, `set`{.python} ou encore les `dict`{.python}.
|
||
|
||
La solution adoptée par Python consiste à interdire le calcul de signature desdits objets.
|
||
Les `tuple`{.python} demeurent des données valides, puisque nous avons la garantie de ne jamais pouvoir
|
||
changer leur contenu, et ainsi leur signature.
|
||
|
||
```python {.numberLines}
|
||
# La fonction hash est disponible par défaut en Python
|
||
print(hash([1, 2, 3])) # Provoque une exception
|
||
```
|
||
|
||
----
|
||
|
||
### Dictionnaires : associations entre clés et valeurs
|
||
|
||
**Dictionnaires** : (`dict`{.python}) C'est un **ensemble** d'associations où l'on définit clés et valeurs. C'est le même
|
||
fonctionnement qu'un dictionnaire lexicographique, où, lorsque vous avez le
|
||
mot (la clé), vous retrouvez la définition s'il y en a une (la valeur). D'autres langages ont des structures
|
||
similaires et appellent ça des `HashMap`{.java} ou des `object`{.javascript}.
|
||
|
||
```python {.numberLines}
|
||
a = {"server1": "192.168.1.2", "server2": "192.168.1.3", "server3": "192.168.1.5"} # serveurs et adresses IP
|
||
b = {8: "Mme Garnier", 10: "M. Dubois", 11: "Mlle Yousfi", 12: "Mme Préjean"} # rendez-vous horaires
|
||
d = {1.2: "flottant", True: "booléen", None: "rien", (1, 2, 3): "tuple"} # clés de plusieurs types
|
||
c = {} # ou dict(), ceci est un dictionnaire vide
|
||
print(a["server1"]) # affiche "192.168.1.2"
|
||
print(b[10]) # affiche "M. Dubois"
|
||
print(b[9]) # provoque une erreur
|
||
```
|
||
|
||
----
|
||
|
||
#### Parcourir un dictionnaire
|
||
|
||
Il est possible de parcourir un dictionnaire avec une boucle `for`{.python}. De base, ce sont les
|
||
clés du dictionnaire qui sont parcourues. Mais il existe des variantes assez pratiques pour
|
||
parcourir un dictionnaire :
|
||
|
||
```python {.numberLines}
|
||
a = {
|
||
"Jérémy": (25, "M", "Lille"),
|
||
"Hélène": (30, "F", "Ambérieu-en-Bugey"),
|
||
"Gwladys": (35, "F", "Nyons"),
|
||
}
|
||
|
||
for prenom in a:
|
||
print(prenom) # affiche uniquement une clé
|
||
print(a[prenom]) # retrouve la valeur associée à la clé
|
||
```
|
||
|
||
----
|
||
|
||
Si vous parcourez une collection dont tous les éléments sont eux-mêmes des séquences
|
||
de `n` éléments, vous pouvez les dépaqueter (**unpacking**) :
|
||
|
||
```python {.numberLines}
|
||
# Avancé, unpacking via la méthode `items`
|
||
|
||
for item in a.items(): # a.items() renvoie ((clé1, valeur1), (clé2, valeur2), …)
|
||
print(item) # est un tuple
|
||
|
||
for key, value in a.items():
|
||
# on peut utiliser 2 variables de boucle si chaque élément parcouru est
|
||
# une séquence de taille 2. Merci l'unpacking !
|
||
print(key, value)
|
||
```
|
||
|
||
----
|
||
|
||
#### Manipuler un dictionnaire
|
||
|
||
Pour changer la valeur associée à une clé existante, ou par la même occasion, associer une valeur à
|
||
une nouvelle clé, il suffit d'écrire :
|
||
|
||
```python {.numberLines}
|
||
a = {"Bordeaux": 250000}
|
||
a["Marseille"] = 800000
|
||
a["Bordeaux"] = 90
|
||
print(a["Bordeaux"]) # Affiche 90
|
||
# On peut supprimer du dictionnaire une association en écrivant
|
||
del a["Marseille"] # plus de clé pour Marseille !
|
||
print("Marseille" in a) # renvoie si la clé "Marseille" existe
|
||
```
|
||
|
||
----
|
||
|
||
### Plus : méthodes de dictionnaires
|
||
|
||
La méthode `get(key, default)`{.python} des dictionnaires renvoie la valeur associée à une clé. Si
|
||
l'on ne passe pas de valeur pour l'argument `default`{.python}, pour une clé introuvable, la méthode
|
||
renvoie `None`{.python} (au lieu de planter comme lorsqu'on écrit `dict[key]`{.python}).
|
||
|
||
```python {.numberLines}
|
||
populations = {"Paris": 2.6e6, "Marseille": 8e5, "Lyon": 5e5, "Bordeaux": 2.5e5}
|
||
print(populations.get("Tarbes")) # renvoie None
|
||
print(populations.get("Mulhouse", -1)) # renvoie -1
|
||
print(populations.get("Marseille", -1)) # renvoie 800 000.0
|
||
```
|
||
|
||
Si l'on passe une valeur pour l'argument `default`{.python}, alors, si la clé n'a pas été trouvée,
|
||
la méthode renvoie la valeur de repli que vous avez définie.
|
||
|
||
----
|
||
|
||
## Compréhensions de listes, tuples etc.
|
||
|
||
Déclarer des listes, tuples, sets et dictionnaires avec la syntaxe de compréhension :
|
||
|
||
```python {.numberLines}
|
||
a = [x * 2 for x in range(100)] # tous les nombres pairs de 0 à 198
|
||
b = [x * 2 for x in range(100) if x != 10] # nombres pairs de 0 à 198 sauf 20
|
||
c = (x for x in range(100)) # pas un tuple mais un générateur, nombres de 0 à 99
|
||
d = {x / 2 for x in range(100)} # on peut faire pareil avec les sets
|
||
pre = {"Jon": None, "Pam": None, "Mel": None, "Kim": None}
|
||
e = {k: pre[k] for k in pre} # on copie pre
|
||
```
|
||
|
||
----
|
||
|
||
## Récapitulatif des collections
|
||
|
||
| Type | Propriétés |
|
||
|---------------|--------------------------------------------------------------------------------------------------------------------------|
|
||
| `list` | `[...,]`{.python}. Séquence dont le contenu est modifiable (ajout, suppression). |
|
||
| `tuple` | `(...,)`{.python}. Séquence dont le contenu est gelé après déclaration. Utilisable comme élément d'un `set`. |
|
||
| `set` | `{...,}`{.python}. Déduplique les éléments et permet des opérations entre ensembles <br/>(union, intersection etc.) |
|
||
| `dict` | `{..:..,}`{.python}. Structure de **recherche** rapide où l'on peut associer des valeurs quelconques à des identifiants. |
|
||
|
||
----
|
||
|
||
### Usages d'exemple
|
||
|
||
Le `tuple`{.python} n'est jamais très important, et il sera souvent plus pratique de travailler sur des listes. Les seuls
|
||
cas de figure qui obligent à utiliser des `tuple`{.python} sont les cas où vous voulez ajouter une séquence comme élément d'un `set`
|
||
ou comme clé d'un `dict`{.python}.
|
||
|
||
- `list`{.python} : si vous avez des données séquentielles à stocker, par exemple lues depuis un fichier.
|
||
- `set`{.python} : si vous souhaitez enlever des doublons, connaître le nombre d'éléments uniques.
|
||
- `dict`{.python} : si vous avez besoin d'une "table de référence" pour stocker des données que vous allez rechercher fréquemment.
|
||
|
||
----
|
||
|
||
# Chaînes de caractères
|
||
|
||
----
|
||
|
||
## Opérations sur les chaînes de caractères
|
||
|
||
Nous n'avons pas vu grand chose sur les chaînes de caractères, mais certaines informations peuvent
|
||
être fréquemment utiles aux développeurs :
|
||
|
||
- [Méthodes sur les objets de type `str`{.python}](https://docs.python.org/3/library/stdtypes.html#string-methods)
|
||
- [Formatage de chaînes via les f-strings](https://zetcode.com/python/fstring/)
|
||
- [Documentation officielle sur le format des interpolations](https://docs.python.org/3/library/string.html#format-string-syntax)
|
||
|
||
```python {.numberLines}
|
||
a = 19
|
||
b = f"Le serveur qui doit être vérifié aujourd'hui est le numéro {a}"
|
||
c = f"Formatage de la variable : {a:f}" # affiché comme flottant (6 chiffres après la virgule)
|
||
```
|
||
|
||
----
|
||
|
||
## Les chaînes de caractères et leurs méthodes
|
||
|
||
```python {.numberLines}
|
||
chaine = "Bonjour"
|
||
print(chaine.upper(), chaine.lower())
|
||
print(chaine.center(50, " ")) # Centre la chaîne originale sur 50 caractères
|
||
```
|
||
|
||
Méthodes qui renvoient une copie modifiée d'une chaîne
|
||
|
||
----
|
||
|
||
## Méthodes fréquemment utiles sur les chaînes de caractères
|
||
|
||
- `upper()`{.python} : renvoie une copie en majuscules
|
||
- `lower()`{.python} : renvoie une copie en minuscules
|
||
- `strip()`{.python} : retire les espaces aux extrêmités
|
||
- `replace(old, new, count=None)`{.python} : remplace `old` par `new`
|
||
- `index(sub, start=None)`{.python} : renvoie la position de `sub`
|
||
- `sub in text`{.python} : renvoie si `sub` est inclus dans `text`
|
||
- `split(sep=None)`{.python} : découpe en liste autour du séparateur, par défaut autour des espaces
|
||
- `str.join(iterable)`{.python} : sert de séparateur et joint une liste de chaînes
|
||
|
||
----
|
||
|
||
```python {.numberLines}
|
||
chaine = "Bonjour, nous sommes le 17 juillet 2063."
|
||
words = chaine.split() # renvoie une liste de chaînes autour des espaces
|
||
rejoin = " ".join(words) # renvoie une chaîne en insérant l'espace comme séparateur
|
||
print(chaine.upper(), chaine.lower(), rejoin)
|
||
print("17 juillet" in chaine)
|
||
```
|
||
|
||
Exemple de `str.split()`{.python} et `str.join()`{.python}
|
||
|
||
----
|
||
|
||
## Bonus : Convertir des données d'un type à un autre
|
||
|
||
Nous avons vu, ici et là, quelques fonctions pour déclarer des valeurs de base, ou convertir des
|
||
valeurs. En voici une liste plus complète :
|
||
|
||
- `bool(val)`{.python}
|
||
- `int(val)`{.python}, `float(val)`{.python}
|
||
- `str(val)`{.python}
|
||
- `list(val)`{.python}, `tuple(val)`{.python}
|
||
- `set(val)`{.python}, `dict(val)`{.python}
|
||
|
||
Toutes ces fonctions renvoient un nouveau booléen, entier, flottant etc. correspondant à une
|
||
conversion de l'expression passée en argument. Cela fonctionne uniquement lorsque la conversion a du sens.
|
||
|
||
----
|
||
|
||
Appelées sans argument, ces fonctions vous renvoient `False`{.python}, `0`{.python}, une chaîne ou une
|
||
collection vide (des valeurs considérées neutres, qui renvoient toujours `False`{.python} lorsqu'on les convertit en booléen).
|
||
|
||
Lorsque vous passez un argument à ces fonctions, elles vous renvoient une nouvelle valeur,
|
||
qui est une conversion de l'argument passé vers le type
|
||
représenté par la fonction. Ce n'est pas toujours possible d'effectuer une conversion; par exemple, il est impossible de
|
||
convertir une liste vers un nombre flottant, ou encore de convertir la chaîne `"bonjour"`{.python} vers un nombre entier.
|
||
|
||
```python {.numberLines}
|
||
converted1 = int(3.14159) # tronque le flottant / retire la partie décimale
|
||
converted3 = float("3.14159") # comprend le texte et génère un flottant
|
||
converted2 = list(1) # erreur
|
||
converted4 = float("salut") # aucun caractère valide pour représenter un nombre, échoue
|
||
```
|
||
|
||
|