La structure générale d'une compréhension de liste est la suivante :
[nouvelle_valeur for element in iterable]
Cette syntaxe permet de créer facilement une nouvelle liste à partir d'une collection existante (iterable) en appliquant une opération à chaque élément (element) de cette collection.
On le verra aussi présenté sous cette forme :
[i for i in iterable]
Nous pouvons aussi ajouter une condition pour filtrer les éléments. Par exemple, si nous voulons uniquement les carrés des nombres pairs :
resultat = [i ** 2 for i in range(10) if i % 2 == 0]
print(resultat)
# [0, 4, 16, 36, 64]
Ici, if i % 2 == 0
sert à inclure seulement les éléments pairs dans notre nouvelle liste.
Nous pouvons même utiliser des conditions ternaires (if...else) pour transformer les valeurs selon un test :
resultat = [i if i % 2 == 0 else -i for i in range(10)]
print(resultat)
# [0, -1, 2, -3, 4, -5, 6, -7, 8, -9]
Cela nous permet de modifier la valeur ajoutée à la liste en fonction d'une condition, tout cela dans la même expression.
La compréhension de liste est donc une forme compacte mais puissante pour manipuler des collections en Python. Nous devons cependant faire attention à ne pas rendre notre code illisible en le surchargeant avec des conditions trop complexes.
liste = []
for n in range(20):
if n % 2 == 0:
liste.append(n)
print(liste)
# [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
Revient à écrire :
liste = [n for n in range(20) if n % 2 == 0]
print(liste)
# [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
On peut aussi inclure une fonction qui n'a rien à voir avec notre compréhension de liste :
[print("Hello") for n in range(20) if n > 10]
# Hello
# Hello
# Hello
# Hello
# Hello
# Hello
# Hello
# Hello
# Hello
# [None, None, None, None, None, None, None, None, None]
Le résultat est une liste de None
car la fonction print()
ne retourne rien.
Comment créer une compréhension de liste
Pour créer une compréhension de liste, par exemple si on est face au code suivant :
liste = []
for _ in range(10):
liste.append('Hey')
# ['Hey', 'Hey', 'Hey', 'Hey', 'Hey', 'Hey', 'Hey', 'Hey', 'Hey', 'Hey']
On part de notre compréhension de liste de base :
[i for i in range(10)]
Et on remplace au fur et à mesure, ici une seule modification sera nécessaire.
liste = ["Hey" for _ in range(10)]
L'utilisation de _
est une convention en Python qui indique que nous créons une variable qui n'a pas vocation à être utilisée (variable jetable).
Une autre méthode consiste à partir de la boucle for :
liste = []
for _ in range(10):
if True:
liste.append('Hey')
print(liste)
# ['Hey', 'Hey', 'Hey', 'Hey', 'Hey', 'Hey', 'Hey', 'Hey', 'Hey', 'Hey']
On copie/colle ce qui est à l'intérieur du append en premier.
['Hey' ]
Puis on copie/colle à partir de for
['Hey' for _ in range(10)]
Puis les conditions ternaires
['Hey' for _ in range(10) if True]
Et voilà le résultat :
liste = ['Hey' for _ in range(10) if True]
print(liste)
# ['Hey', 'Hey', 'Hey', 'Hey', 'Hey', 'Hey', 'Hey', 'Hey', 'Hey', 'Hey']
Exemple avec le module random
L'exemple suivant nous permet de créer une liste de 100 chiffres compris entre 0 et 10 arrondis aléatoires à l'aide du module random.
from random import random
liste = [round(random() * 10) for _ in range(100)]
Avec une boucle for standard :
import random
liste = []
for _ in range(100):
liste.append(round(random.random() * 10))
print(liste)
Imports de modules en Python
Commençons par observer ensemble les deux manières principales d'importer des modules en Python et comprenons les différences que cela implique dans notre manière de coder.
Lorsque nous écrivons import module
, nous faisons entrer tout le module dans notre script, mais sans en importer directement les fonctions ou objets dans notre espace de noms. Cela signifie que nous devons toujours utiliser le nom du module comme préfixe pour accéder à ce qu'il contient. Prenons un exemple concret avec le module math :
import math
resultat = math.sqrt(16)
print(resultat)
# 4.0
Cette méthode est claire : chaque fois que nous voyons math.sqrt
, nous savons exactement d'où vient cette fonction. Cela réduit le risque de confusion si une autre partie de notre code définit aussi une fonction appelée sqrt. C'est donc une approche que nous privilégierons pour maintenir un code lisible et sûr.
L'autre manière, c'est d'écrire from module import objet
. Ici, nous importons seulement un élément précis du module et nous pouvons l'utiliser directement, sans préfixe :
from math import sqrt
resultat = sqrt(16)
print(resultat)
# 4.0
C'est plus rapide à écrire, surtout si nous utilisons souvent cette fonction. Mais cela comporte aussi un risque : si un autre module ou une autre partie de notre code contient un objet du même nom, nous pourrions écraser l'un avec l'autre sans nous en rendre compte. Il faut donc rester vigilant avec cette méthode, même si elle rend notre code plus concis.
Enfin, il existe la forme from module import *
. Cette manière d'importer tout le contenu d'un module sans préfixe est fortement déconseillée, car elle injecte tous les noms dans notre espace global sans distinction, ce qui peut rendre le code difficile à maintenir ou déboguer. Par exemple :
from math import *
resultat = sqrt(16)
print(resultat)
# 4.0
Même si cela fonctionne, nous perdons la trace de l'origine des fonctions utilisées, ce qui peut poser problème dans des projets plus complexes.
En conclusion, nous préférerons import module
pour garder un code explicite et sécurisé.
Nous utiliserons from module import objet
lorsqu'un objet est utilisé très fréquemment, à condition de bien vérifier qu'aucun conflit de noms ne peut survenir.
Quant à from module import *
, nous éviterons son usage autant que possible pour préserver la clarté de notre code.
Exemple avec l'opérateur walrus
Une boucle for peut être utilisée tout comme une compréhension de liste, il n'y a aucune obligation.
Voici un autre exemple :
from random import random
liste = []
for _ in range(10):
n = round(random() * 10)
if n % 2 == 0:
liste.append(n)
print(liste)
# [6, 4, 4, 8, 6, 8, 2]
Compréhension de liste :
from random import random
liste = [n for _ in range(10) if (n := round(random() * 10)) % 2 == 0]
print(liste)
# [8, 8, 4, 4, 6]
Ici, on utilise l'opérateur walrus (:=
) introduit en Python 3.8 pour affecter une variable dans une expression.
Cela permet de rester fidèle à notre code initial tout en profitant de la syntaxe compacte des compréhensions de liste.
Sinon, nous aurions pu ruser en utilisant une approche différente :
liste = [round(random() * 5) * 2 for _ in range(10)]
print(liste)
# [0, 6, 8, 10, 0, 2]
Compréhensions de liste déconseillées
liste = []
for i in range(3):
for j in range(3):
liste.append((i, j))
print(liste)
# [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]
L'équivalent en compréhension de liste serait trop verbeux et difficile à comprendre.
liste = [(i, j) for i in range(3) for j in range(3)]
print(liste)
# [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]
Cette utilisation est donc déconseillée.
Autre exemple plus poussé :
liste = []
for i in range(3):
if i % 2 == 0:
for j in 'ABC':
liste.append((i, j))
print(liste)
# [(0, 'A'), (0, 'B'), (0, 'C'), (2, 'A'), (2, 'B'), (2, 'C')]
Revient à :
liste = [(i, j) for i in range(3) for j in "ABC" if i % 2 == 0]
print(liste)
# [(0, 'A'), (0, 'B'), (0, 'C'), (2, 'A'), (2, 'B'), (2, 'C')]
Dans ce type d'utilisation on mettra les opérateurs ternaires à la fin de l'expression, il peut également y en avoir plusieurs dans la même compréhension de liste.
liste = [(i, j) for i in range(3) for j in "ABC" if i % 2 == 0 if j != "A"]
print(liste)
# [(0, 'B'), (0, 'C'), (2, 'B'), (2, 'C')]
Différence entre un tuple et un set en Python
Un tuple est une séquence ordonnée d'éléments. Cela signifie que les éléments ont un ordre fixe et que cet ordre ne change pas. De plus, les tuples sont immutables, c'est-à-dire qu'une fois qu'on les a créés, on ne peut plus en modifier les éléments. Un tuple peut contenir des doublons et peut contenir tout type d'objet : nombres, chaînes de caractères, listes, autres tuples, etc.
Regardons un exemple :
mon_tuple = (1, 2, 3, 2)
print(mon_tuple)
print(mon_tuple[0])
# (1, 2, 3, 2)
# 1
Ici, nous voyons que le tuple conserve l'ordre des éléments et accepte les doublons. Nous pouvons également accéder à un élément en utilisant un index.
Passons maintenant au set. Un set, ou ensemble en français, est une collection non ordonnée et non indexée d'éléments uniques. Cela signifie que les doublons ne sont pas autorisés et que les éléments ne sont pas accessibles par un index. En revanche, un set est modifiable : on peut y ajouter ou retirer des éléments après sa création.
Voici un exemple :
mon_set = {1, 2, 3, 2}
print(mon_set)
# {1, 2, 3}
Nous remarquons que l'élément 2 en double est automatiquement supprimé, car un set ne garde qu'une seule occurrence de chaque élément. De plus, l'ordre d'affichage n'est pas garanti.
En résumé, si nous avons besoin d'une séquence ordonnée et immuable, nous choisissons un tuple, en revanche si nous avons besoin d'un ensemble non ordonné d'éléments uniques, nous utilisons un set.
Ces différences influencent fortement le choix de l'un ou l'autre selon les besoins de notre programme.