fiche mémo : 2.5. Boucles et compréhensions de listes.¶
Boucles for et while, et une fonctionnalité Python très appréciée : les compréhensions de listes.¶
Vincent GODARD - V2.1 - 30/03/2025¶
Cours Introduction à la programmation¶
Département de géographie - L3 - Université de Paris 8¶
Traduction librement (largement) inspirée de : https://www.kaggle.com/code/colinmorris/loops-and-list-comprehensions
Sources :
Kaggle : https://www.kaggle.com/learn
Python Tutoriel : https://python.doctor/page-comprehension-list-listes-python-cours-debutants
Cours de Python : https://www.docstring.fr/glossaire/comprehension-de-liste/
Téléchargement des documents nécessaires :
Dossier compressé à télécharger => (http://julienas.ipt.univ-paris8.fr/vgodard/pub/enseigne/intropro/python/data/Madeira2013.7z).
planets = ['Mercury', 'Venus', 'Earth', 'Mars', 'Jupiter', 'Saturn', 'Uranus', 'Neptune']
for planet in planets:
print(planet, end=' ') # imprimer tout sur la même ligne
La boucle for spécifie
• le nom de la variable à utiliser (ici, planet)
• l'ensemble des valeurs sur lequel boucler (ici, planets)
On utilise le mot « in » pour les relier.
L'objet à droite de « in » peut être n'importe quel objet itératif. En résumé, s'il peut être considéré comme un groupe d'éléments, on peut probablement le parcourir. Outre les listes, on peut itérer sur les éléments d'un tuple :
# Multiplier les éléments d'un tupple par le résultat du précédent produit.
multiplicands = (2, 2, 2, 3, 3, 5)
product = 1
for mult in multiplicands:
product = product * mult
product
Vous pouvez même parcourir chaque caractère d'une chaîne :
# La stéganographie est la pratique consistant à dissimuler un fichier,
# un message, une image ou une vidéo dans un autre fichier, message, image ou vidéo.
s = 'steganograpHy is the practicE of conceaLing a file, message, image, or video within another fiLe, message, image, Or video.'
msg = ''
# affiche toutes les lettres majuscules de s, une à la fois
for char in s:
if char.isupper(): # isupper = majuscule
print(char, end='')
1.2. Boucles avec range()¶¶
range() est une fonction qui renvoie une séquence de nombres. Elle s'avère très utile pour écrire des boucles.
Par exemple, si nous voulons répéter une action 5 fois :
# Pour préparer un sodage (par exemple)
for i in range(5):
print("Combien d'étudiants ont suivi le cours en visio", i,"fois =")
1.3. Boucles avec while¶¶
L'autre type de boucle en Python est la boucle while, qui itère jusqu'à ce qu'une condition soit remplie :
i = 0
while i < 10:
print(i, end=' ')
i += 1 # augmenter la valeur de i de 1
L'argument de la boucle while est évalué comme une instruction booléenne et la boucle est exécutée jusqu'à ce que l'instruction soit évaluée à False.
2. Compréhensions de liste¶¶
Les compréhensions de liste sont l'une des fonctionnalités les plus appréciées et uniques de Python. Le moyen le plus simple de les comprendre est probablement de regarder quelques exemples :
squares = [n**2 for n in range(10)]
squares
Voici comment nous ferions la même chose sans compréhension de liste :
squares = [] # création d'une liste vide
for n in range(10):
squares.append(n**2) # que l'on remplie avec .append
squares
Nous pouvons également ajouter une condition if :
## Recherche dans la liste des planètes des planètes de moins de 6 caractères
short_planets = [planet for planet in planets if len(planet) < 6]
short_planets
(Si vous connaissez le langage SQL, vous pourriez considérer cela comme une clause « WHERE »).
Voici un exemple de filtrage avec une condition « if » et application d'une transformation à la variable de boucle :
# str.upper() renvoie une version tout en majuscules d'une chaîne
loud_short_planets = [planet.upper() + '!' for planet in planets if len(planet) < 6]
loud_short_planets
Les gens les écrivent généralement sur une seule ligne, mais vous trouverez peut-être la structure plus claire lorsqu'elle est divisée sur 3 lignes :
[
planet.upper() + '!' ## SELECT
for planet in planets ## FROM
if len(planet) < 6 ## WHERE
]
(En poursuivant l'analogie SQL, vous pourriez considérer ces trois lignes comme SELECT, FROM et WHERE.)
L'expression de gauche n'implique pas nécessairement la variable de boucle (même si cela paraît plutôt inhabituel). À votre avis, quel résultat l'expression ci-dessous donnera-t-elle ? Exécuter pour vérifier.
[32 for planet in planets]
Les compréhensions de listes combinées à des fonctions telles que min, max et sum peuvent donner lieu à des solutions impressionnantes en une seule ligne pour des problèmes qui nécessiteraient autrement plusieurs lignes de code.
Par exemple, comparez les deux cellules de code suivantes qui font la même chose.
def count_negatives(nums):
"""Renvoie le nombre de nombres négatifs dans la liste donnée.
>>> count_negatives([5, -1, -2, 0, 3])
2
"""
n_negative = 0
for num in nums:
if num < 0:
n_negative = n_negative + 1
return n_negative
## Si on veut que le résultat s'affiche, il faut rajouter :
# Appel de la fonction avec une liste donnée
resultat = count_negatives([5, -1, -2, 0, 3])
# Affichage du résultat
print(resultat)
Voici une solution utilisant une compréhension de liste pour compter les nombres négatifs dans une liste donnée :
# Définition de la fonction
def count_negatives(nums):
return len([num for num in nums if num < 0])
# Appel de la fonction avec une liste donnée
resultat = count_negatives([5, -1, -2, 0, 3])
# Affichage du résultat
print(resultat)
Explication :
• La compréhension de liste [num for num in nums if num < 0] crée une nouvelle liste contenant uniquement les nombres négatifs de la liste nums.
• La fonction len() calcule la longueur de cette liste (le nombre d'éléments négatifs).
• Le résultat est ensuite affiché avec print.
Bien mieux, non ?
Si notre seul souci est de minimiser la longueur de notre code, cette troisième solution est encore meilleure !
def count_negatives(nums):
# Rappel : dans les exercices « booléens et conditionnels », nous avons appris une particularité de
# Python où il calcule quelque chose comme True + True + False + True pour être égal à 3.
return sum([num < 0 for num in nums])
# Pour afficher le résultat, ajouter :
# Appel de la fonction avec une liste donnée
resultat = count_negatives([5, -1, -2, 0, 3])
# Affichage du résultat
print(resultat)
Explication du script :
1. Compréhension de liste :
• [num < 0 for num in nums] génère une liste de valeurs booléennes (True ou False) en vérifiant si chaque élément de nums est négatif.
• Par exemple, pour [5, -1, -2, 0, 3], cela produira [False, True, True, False, False].
2. Somme des valeurs booléennes :
• En Python, les valeurs True et False sont équivalentes à 1 et 0, respectivement.
• La fonction sum() additionne ces valeurs. Dans l'exemple ci-dessus : False + True + True + False + False = 0 + 1 + 1 + 0 + 0 = 2.
Ainsi, la fonction retourne le nombre total de nombres négatifs dans la liste
Laquelle de ces solutions est la « meilleure » ? La réponse est totalement subjective.
Résoudre un problème avec moins de code est toujours une bonne chose, mais il est important de garder à l'esprit les lignes suivantes tirées de "The Zen of Python" (https://en.wikipedia.org/wiki/Zen_of_Python)%C2%A0:
• La lisibilité est essentielle.
• L'explicite est préférable à l'implicite.
Utilisez donc ces outils pour créer des programmes compacts et lisibles. Mais lorsque vous devez choisir, privilégiez un code facile à comprendre.
3. Mise en pratique¶
Mise en pratique par modification des noms de fichiers avec des boucles "for"¶
Comment modifier rapidement et "industriellement" des noms de fichiers avec des boucles dans l'arborescence de Windows ?
3.1. Lecture des coordonnées dans un bloc-notes¶
Lecture des coordonnées d'un premier fichier gpx [https://fr.wikipedia.org/wiki/GPX_(format_de_fichier)] situé dans le répertoir du premier jour de randonnée (130429).
Double cliquer sur "track-rando1_SaoJorge.gpx" et choisir d'ouvrir avec Bloc-notes, Notepad...
Le fichier "track-rando1_SaoJorge.gdb" est généré par MapSource, une application fournie avec les récepteurs Garmin (ici le Garmin 60 utilisé pour le TD).
Récupérer deux coordonnées lat et long et regarder sur Google Map où est-ce !
3.2. Renommer des fichiers avec Python¶
Nous avons vu lors de la première séance comment renommer des fichiers en "ligne de commandes" (avec RENAME par exemple, cf. https://blog.alphorm.com/utiliser-les-commandes-cmd-sur-windows ou https://kevinpierin.wordpress.com/wp-content/uploads/2014/04/liste-des-commandes-cmd-s-linvite-de-commandes.pdf).
C'est également possible avec Python depuis un Jupyter NoteBook.
Comme les fichiers semblent concerner Madère (Madeira) en 2013, il faudrait ajouter un préfixe au fichiers gpx du genre "Mad13_". Voici comment Python peut nous aider.
# Ajout dans un répertoire d'un préfixe à tous les fichiers
# Import des bibliothèques (se fait habituellement au début du NoteBook)
import os
# Chemin du répertoire contenant les fichiers (attention aux sous répertoires)
repertoire = 'data/130429'
# Parcourir tous les fichiers du répertoire data/130429
# Explication de quelques éléments dans la boucle :
# os.listdir(repertoire) = fct qui retourne une liste contenant les noms de toutes les entrées (fichiers et sous-répertoires) présentes dans le répertoire spécifié.
# os.listdir ne distingue pas entre fichiers et répertoires (impose des fonctions comme os.path.isfile() ou os.path.isdir() pour les différencier).
for fichier in os.listdir(repertoire):
chemin_complet = os.path.join(repertoire, fichier) # os.path.join = fct qui combine plusieurs parties d'un chemin (file et dir) en un chemin complet (pour win, Mac et Linux)
# Vérifier si c'est un fichier (et non un sous-dossier)
# os.path.isfile(chemin_complet) = fct qui vérifie si le chemin spécifié (path) correspond à un fichier existant.
# Renvoie "true" si le chemin pointe vers un fichier et "False" sinon (répertoire ou si le fichier n'existe pas).
if os.path.isfile(chemin_complet):
# Nouveau nom avec le préfixe "Mad13_"
nouveau_nom = os.path.join(repertoire, f"Mad13_{fichier}") # f-string (formatage de chaîne en Python) pour concaténer "Mad13_" au début du nom du fichier.
# Si tout es vérifié au dessus, renomme le fichier
os.rename(chemin_complet, nouveau_nom)
print("Tous les fichiers ont été renommés avec le préfixe 'Mad13_'.")
En fait, ce ne sont pas tous les fichiers que l'on voulait renommer, mais seulement ceux avec suffixe "gpx" !
Deux solutions :
3.2.1. Enlever le préfixe 'Mad13_' aux suffixes ".txt" et ".gdb"¶
# Retrait, dans un répertoire, d'un préfixe sous condition
# Import des bibliothèques
#import os
# Chemin du répertoire contenant les fichiers
repertoire = 'data/130429'
# Parcourir tous les fichiers du répertoire
for fichier in os.listdir(repertoire):
chemin_complet = os.path.join(repertoire, fichier)
# Vérifier si c'est un fichier (et non un sous-dossier)
if os.path.isfile(chemin_complet):
# Vérifier si le fichier commence par "Mad13_" et n'a pas le suffixe ".gpx"
if fichier.startswith("Mad13_") and not fichier.endswith(".gpx"):
# Nouveau nom sans le préfixe "Mad13_"
nouveau_nom = os.path.join(repertoire, fichier[len("Mad13_"):])
# Renommer le fichier
os.rename(chemin_complet, nouveau_nom)
print("Les fichiers ont été renommés en supprimant le préfixe 'Mad13_' sauf ceux avec le suffixe '.gpx'.")
3.2.2. N'apposer, dès le départ, le préfixe 'Mad13_' qu'aux fichiers avec un suffixe en ".gpx"¶
# Ajout dans un répertoire du préfixe "Mad13_" aux seuls fichiers ayant le suffixe ".gpx"
# Import des bibliothèques
#import os
# Chemin du répertoire contenant les fichiers (attention aux sous-répertoires)
repertoire = 'data/130429'
# Parcourir tous les fichiers du répertoire
for fichier in os.listdir(repertoire):
chemin_complet = os.path.join(repertoire, fichier)
# Vérifier si c'est un fichier (et non un sous-dossier)
if os.path.isfile(chemin_complet):
# Vérifier si le fichier a le suffixe ".gpx" et ne commence pas déjà par "Mad13_"
if fichier.endswith('.gpx') and not fichier.startswith('Mad13_'):
# Nouveau nom avec le préfixe "Mad13_"
nouveau_nom = os.path.join(repertoire, f"Mad13_{fichier}")
# Renommer le fichier
os.rename(chemin_complet, nouveau_nom)
print("Les fichiers avec le suffixe '.gpx' ont été renommés avec le préfixe 'Mad13_'.")
3.3. Renommer des fichiers de plusieurs répertoires¶
Que faudrait-il faire pour ajouter "Mad13_" à touts les fichiers ".gpx" de plusieurs sous-répertoires du répertoire "data" ?
# Import des bibliothèques
import os
# Chemin du répertoire racine contenant les sous-répertoires
racine = 'data'
# Parcourir tous les sous-répertoires ("_") du répertoire racine ("data")
for sous_repertoire, _, fichiers in os.walk(racine):
# Parcourir tous les fichiers dans le sous-répertoire
for fichier in fichiers:
chemin_complet = os.path.join(sous_repertoire, fichier)
# Vérifier si c'est un fichier (et non un sous-dossier)
if os.path.isfile(chemin_complet):
# Vérifier si le fichier a le suffixe ".gpx" et qu'il ne commence pas par "Mad13_"
if fichier.endswith('.gpx') and not fichier.startswith('Mad13_'):
# Nouveau nom avec le préfixe "Mad13_"
nouveau_nom = os.path.join(sous_repertoire, f"Mad13_{fichier}")
# Renommer le fichier
os.rename(chemin_complet, nouveau_nom)
print("Les fichiers avec le suffixe '.gpx' ont été renommés avec le préfixe 'Mad13_' dans tous les sous-répertoires.")
Si un message d'erreur du type : "Impossible de renommer wpt-rando1_SaoJorge.gpx. Erreur : [WinError 32] Le processus ne peut pas accéder au fichier car ce fichier est utilisé par un autre processus: 'data\wpt-rando1_SaoJorge.gpx' -> 'data\Mad13_wpt-rando1_SaoJorge.gpx'" s'affiche, le problème vient peut-être d'un verrouillage temporaire par un processus externe (par exemple, Jupyter Notebook lui-même). Redémarrer l'environnement ou utiliser une pause (time.sleep) devrait suffire dans la plupart des cas.