Maîtrise du développement Python
Structures de données avancées
Optimiser avec les compréhensions
Vous connaissez déjà les boucles pour construire des listes. Mais il existe une manière plus concise et souvent plus rapide : les compréhensions de listes. Elles permettent de créer une nouvelle liste en appliquant une expression à chaque élément d'un itérable.
La syntaxe est élégante : [expression for élément in itérable if condition]. La partie if est optionnelle mais très utile pour filtrer des éléments.
# Méthode classique avec une boucle for
carrés_pairs = []
for i in range(10):
if i % 2 == 0:
carrés_pairs.append(i**2)
# Avec une compréhension de liste
carrés_pairs_comp = [i**2 for i in range(10) if i % 2 == 0]
print(carrés_pairs) # Affiche [0, 4, 16, 36, 64]
print(carrés_pairs_comp) # Affiche [0, 4, 16, 36, 64]
Le même principe s'applique aux dictionnaires, ce qui est parfait pour transformer une structure de données en une autre. Par exemple, pour créer un dictionnaire où les clés sont des nombres et les valeurs leurs carrés :
# Crée un dictionnaire {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
dict_carrés = {x: x**2 for x in range(5)}
print(dict_carrés)
# Inverser un dictionnaire de noms et d'identifiants
utilisateurs = {'Alice': 101, 'Bob': 102, 'Charlie': 103}
ids_utilisateurs = {id: nom for nom, id in utilisateurs.items()}
print(ids_utilisateurs) # Affiche {101: 'Alice', 102: 'Bob', 103: 'Charlie'}
La puissance des slices
Le découpage (slicing) est un outil puissant pour extraire des sous-séquences. La syntaxe de base est [début:fin], mais la version étendue [début:fin:pas] offre encore plus de contrôle.
Un cas d'usage courant est l'inversion d'une séquence. Avec un pas de -1, vous pouvez inverser une liste ou une chaîne de caractères en une seule opération.
ma_liste = [10, 20, 30, 40, 50, 60]
# Extraire un élément sur deux
print(ma_liste[::2]) # Affiche [10, 30, 50]
# Inverser la liste
print(ma_liste[::-1]) # Affiche [60, 50, 40, 30, 20, 10]
Les slices peuvent aussi être utilisées pour modifier une liste (in-place), c'est-à-dire sans créer une nouvelle copie de la liste. C'est très efficace pour remplacer, supprimer ou insérer plusieurs éléments à la fois.
ma_liste = [10, 20, 30, 40, 50, 60]
# Remplacer les éléments d'indice 1 et 2
ma_liste[1:3] = [25, 35]
print(ma_liste) # Affiche [10, 25, 35, 40, 50, 60]
# Insérer des éléments sans en supprimer
ma_liste[1:1] = [12, 15, 18]
print(ma_liste) # Affiche [10, 12, 15, 18, 25, 35, 40, 50, 60]
# Vider la liste
ma_liste[:] = []
print(ma_liste) # Affiche []
Ensembles pour l'efficacité
Les ensembles (set) sont des collections non ordonnées d'éléments uniques. Leur principal avantage est leur rapidité pour vérifier la présence d'un élément. C'est beaucoup plus rapide que de chercher dans une liste, surtout si elle est longue.
Ils excellent dans les opérations ensemblistes classiques que l'on retrouve en mathématiques.
# Définition de deux ensembles
web_devs = {'Alice', 'Bob', 'Charlie'}
python_devs = {'Charlie', 'David', 'Eve'}
# Union : tous les développeurs uniques
print(web_devs | python_devs)
# {'Eve', 'Bob', 'Alice', 'Charlie', 'David'}
# Intersection : ceux qui sont dans les deux groupes
print(web_devs & python_devs)
# {'Charlie'}
# Différence : développeurs web qui ne font pas de Python
print(web_devs - python_devs)
# {'Bob', 'Alice'}
# Différence symétrique : ceux qui sont dans un groupe, mais pas les deux
print(web_devs ^ python_devs)
# {'Eve', 'Bob', 'Alice', 'David'}
Dictionnaires complexes et performance
Les dictionnaires peuvent contenir d'autres dictionnaires, créant ainsi des structures imbriquées complexes, idéales pour représenter des données structurées comme du JSON. Accéder à une valeur nécessite alors d'enchaîner les clés.
donnees_utilisateur = {
'id': 101,
'nom': 'Alice',
'contact': {
'email': 'alice@example.com',
'telephone': '555-1234'
}
}
# Accéder à une valeur imbriquée
email = donnees_utilisateur['contact']['email']
print(email) # Affiche alice@example.com
Tenter d'accéder à une clé qui n'existe pas provoque une erreur KeyError. Pour éviter cela, on peut utiliser la méthode .get() qui renvoie None par défaut, ou une valeur de notre choix.
Pour des cas plus complexes, comme compter des occurrences, le defaultdict du module collections est très pratique. Il fournit une valeur par défaut pour les clés inexistantes, ce qui simplifie le code.
from collections import defaultdict
# Créer un defaultdict qui renvoie 0 si la clé n'existe pas
compteur = defaultdict(int)
mots = ['pomme', 'banane', 'pomme', 'orange', 'banane', 'pomme']
for mot in mots:
compteur[mot] += 1 # Pas besoin de vérifier si la clé existe !
print(compteur)
# defaultdict(<class 'int'>, {'pomme': 3, 'banane': 2, 'orange': 1})
Le choix de la bonne structure de données a un impact direct sur la performance de votre code. (souvent notée avec la notation Grand O) décrit comment le temps d'exécution d'une opération évolue avec la taille des données.
| Opération | Liste (list) | Ensemble (set) | Dictionnaire (dict) |
|---|---|---|---|
| Accès (par indice/clé) | N/A | ||
| Recherche (par valeur) | (par clé) | ||
| Insertion | (à la fin) | ||
| Suppression |
Comme le montre ce tableau, pour des recherches, insertions ou suppressions fréquentes, les ensembles et les dictionnaires sont bien plus performants que les listes.
Quelle sera la sortie du code Python suivant ? [i * 2 for i in range(5) if i % 2 == 0]
Comment peut-on inverser efficacement une liste ma_liste en utilisant la technique du découpage (slicing) ?
Savoir quand utiliser une liste, un dictionnaire ou un ensemble est essentiel pour écrire du code Python non seulement fonctionnel, mais aussi efficace et lisible.