Skip to article frontmatterSkip to article content

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

# 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 :

# 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 :

expression génératrice

pour éviter ces problèmes: utiliser une genexpr

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

# 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)
200

compréhension ou genexpr ?

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