compréhensions¶
très fréquemment on veut construire un mapping
map¶
pour appliquer une fonction à un ensemble de valeurs
map + filter¶
idem en excluant certaines entrées
compréhension de liste¶
c’est le propos de la compréhension (de liste):
[expr(x) for x in iterable]qui est équivalent à
result = []
for x in iterable:
result.append(expr(x))compréhension de liste avec filtre¶
si nécessaire on peut ajouter un test de filtre:
[expr(x) for x in iterable
if condition(x)]qui est équivalent à
result = []
for x in iterable:
if condition(x):
result.append(expr(x))compréhensions de liste - exemple 1¶
sans filtre
# la liste des carrés
# des entiers entre 0 et 5
[x**2 for x in range(6)][0, 1, 4, 9, 16, 25]# si on décortique
result = []
for x in range(6):
result.append(x**2)
result[0, 1, 4, 9, 16, 25]compréhensions de liste - exemple 2¶
avec filtre
# la liste des cubes
# des entiers pairs entre 0 et 5
[x**2 for x in range(6) if x % 2 == 0][0, 4, 16]# si on décortique
result = []
for x in range(6):
if x % 2 == 0:
result.append(x**3)
result[0, 8, 64]compréhension de liste - imbrications¶
on peut imbriquer plusieurs niveaux de boucle
la profondeur du résultat dépend du nombre de
[
et pas du nombre defor
# une liste toute plate comme résultat
# malgré deux boucles for imbriquées
[10*x + y for x in (1, 2) for y in (1, 2)][11, 12, 21, 22]compréhensions imbriquées - exemple¶
l’ordre dans lequel se lisent les compréhensions imbriquées:
il faut imaginer des for imbriqués dans le même ordre
[10*x + y for x in range(1, 5)
if x % 2 == 0
for y in range(1, 5)
if y % 2 == 1][21, 23, 41, 43]# est équivalent à
# (dans le même ordre)
L = []
for x in range(1, 5):
if x % 2 == 0:
for y in range(1, 5):
if y % 2 == 1:
L.append(10*x + y)
L[21, 23, 41, 43]compréhension d’ensemble¶
même principe exactement, mais avec des {} au lieu des []
# en délimitant avec des {}
# on construit une
# compréhension d'ensemble
{x**2 for x in range(-6, 7)
if x % 2 == 0}{0, 4, 16, 36}# rappelez-vous que {} est un dict
result = set()
for x in range(-6, 7):
if x % 2 == 0:
result.add(x**2)
result{0, 4, 16, 36}compréhension de dictionnaire¶
syntaxe voisine, avec un : pour associer clé et valeur
# sans filtre
{x : x**2 for x in range(4)}{0: 0, 1: 1, 2: 4, 3: 9}# avec filtre
{x : x**2 for x in range(4) if x%2 == 0}{0: 0, 2: 4}exemple : créer un index par une compréhension¶
un idiome classique :
on a une liste d’éléments - beaucoup, genre 106
on veut pouvoir accéder en temps constant
à un élément à partir d’un idsolution: créer un dictionnaire - qu’on appelle un index
(comme dans les bases de données)
# créer un dict qui permet un accès direct à partir du nom
personnes = [
{'nom': 'Martin', 'prenom': 'Julie', 'age': 18},
{'nom': 'Dupont', 'prenom': 'Jean', 'age': 32},
{'nom': 'Durand', 'prenom': 'Pierre', 'age': 25},
]
# l'idiome pour créer un index
index = {personne['nom']: personne for personne in personnes}
index{'Martin': {'nom': 'Martin', 'prenom': 'Julie', 'age': 18},
'Dupont': {'nom': 'Dupont', 'prenom': 'Jean', 'age': 32},
'Durand': {'nom': 'Durand', 'prenom': 'Pierre', 'age': 25}}# le concept est le même que dans une base de données
# en termes d'accès rapide à partir du nom qui jour le rôle d'id
index['Martin']{'nom': 'Martin', 'prenom': 'Julie', 'age': 18}expression génératrice¶
performance des compréhensions¶
la compréhension n’est pas l’arme absolue ! elle a un gros défaut, c’est qu’on va toujours :
parcourir tout le domaine
et allouer de la mémoire
au moment où on évalue la compréhension,
i.e. avant même de faire quoi que ce soit d’autre
expression génératrice¶
pour éviter ces problèmes: utiliser une genexpr
ça se présente un peu comme une compréhension de liste
mais avec des
()à la place des[]supporte les
ifet les imbrications
exactement comme les compréhensions
data = [0, 1]# compréhension
C = [10*x + y for x in data for y in data]
for y in C:
print(y)0
1
10
11
# genexpr
G = (10*x + y for x in data for y in data)
for y in G:
print(y)0
1
10
11
les genexprs sont des itérateurs¶
même “contenu” que la compréhension
mais pas la même implémentation: une genexpr est de type
generator(en particulier c’est un itérateur)
# compréhension
C = [x**2 for x in range(4)]
type(C), C(list, [0, 1, 4, 9])# genexpr
G = (x**2 for x in range(4))
type(G), G(generator, <generator object <genexpr> at 0x7f99b8c398a0>)# une compréhension est une vraie liste
C2 = [x**2 for x in range(100_000)]
import sys
sys.getsizeof(C2)800984# les genexprs sont des itérateurs
# et donc sont tout petits
G2 = (x**2 for x in range(100_000))
sys.getsizeof(G2)200compréhension ou genexpr ?¶
les compréhensions de dictionnaire et d’ensemble sont souvent justifiées
par contre, pour les listes: toujours bien se demander si on a vraiment besoin de construire la liste
ou si au contraire on a juste besoin d’itérer dessus (souvent une seule fois d’ailleurs)
si on a vraiment besoin de cette liste, alors la compréhension est OK
mais dans le cas contraire il faut préférer un itérateur, c’est le propos de l’expression génératrice
qui souvent revient à remplacer
[]par()- ou même juste enlever les[]
apprenez à bien choisir entre compréhension et genexpr (les deux sont utiles)
# remplissons une classe imaginaire
from random import randint
matieres = ('maths', 'français', 'philo')
def notes_eleve_aleatoires():
return {matiere: randint(0, 20) for matiere in matieres}# ici je crée une compréhension; pourquoi ?
notes_classe = [notes_eleve_aleatoires() for _ in range(4)]
notes_classe[{'maths': 12, 'français': 20, 'philo': 1},
{'maths': 3, 'français': 13, 'philo': 7},
{'maths': 17, 'français': 10, 'philo': 14},
{'maths': 13, 'français': 20, 'philo': 8}]# pour calculer la moyenne de la classe en maths
# pas besoin de garder les résultats intermédiaires
# du coup, on fabrique une genexpr
# en toute rigueur il aurait fallu écrire ceci
sum((notes_eleve['maths'] for notes_eleve in notes_classe)) / len(notes_classe)11.25# mais la syntaxe nous permet de nous en affranchir
# (remarquez une seul niveau de parenthèses, et l'absence de [])
sum(notes_eleve['maths'] for notes_eleve in notes_classe) / len(notes_classe)11.25