Compréhensions — une boucle sur une ligne
Compréhensions de liste, dictionnaire et ensemble, la différence entre un `if` de filtrage et un `if/else` de choix, et quand une compréhension est le mauvais choix.
Comment voulez-vous apprendre ?
Le problème que nous résolvons
Une forme de code n'a cessé de réapparaître au cours des six derniers chapitres :
marks = [72, 45, 90, 33]
doubled = []
for mark in marks:
doubled.append(mark * 2)
print(doubled)
doubled = [mark * 2 for mark in marks]
print(doubled)[144, 90, 180, 66]
[144, 90, 180, 66]Les quatre lignes ci-dessus et la ligne unique ci-dessous font exactement la même chose.
Trois de ces quatre lignes ne sont pas le travail, c'est la paperasse — créer une liste vide, parcourir la source, ajouter à chaque fois. La seule vraie décision est mark * 2. Tout le reste est identique à chaque fois.
Une compréhension supprime cette répétition. Ce n'est pas une nouvelle capacité — c'est ce que vous pouviez déjà écrire, en moins de mots. C'est aussi d'où vient sa limite, et c'est le sujet de la dernière section de ce chapitre : raccourcir ce qui devient plus clair en étant raccourci, pas tout.
À la fin de ce chapitre vous pourrez
- Écrire et lire une compréhension de liste
- Filtrer avec
if, et choisir avecif/else - Écrire des compréhensions de dictionnaire et d'ensemble
- Dire quand une compréhension est le mauvais choix
- Décomposer une compréhension illisible pour en refaire une boucle
Prérequis : Données imbriquées — une liste de dictionnaires.
La forme
[ result for item in source ]Ici result est ce que vous voulez construire, item est une chose à chaque tour, et source est la liste (ou autre collection) d'où viennent les choses.
Lu à voix haute, c'est une phrase ordinaire : « `mark 2 pour chaque mark dans marks` »*.
Une façon de la retenir : lisez d'abord la partie for, puis le début. Savoir d'où viennent les choses rend facile de voir ce qui est en train d'être construit.
Filtrer
Un if à la fin ne garde que les choses qui correspondent :
marks = [72, 45, 90, 33]
passed = [mark for mark in marks if mark >= 40]
print(passed)[72, 45, 90]Ici la première partie est juste mark — pas de calcul, prise telle quelle. Filtrer et transformer peuvent être faits ensemble :
names = [" rafi ", "AHMED", " dia"]
clean = [name.strip().lower() for name in names if name.strip()]
print(clean)['rafi', 'ahmed', 'dia']La condition est name.strip() sans aucune comparaison. Rappelez-vous la règle de véracité du chapitre huit : une chaîne vide est fausse. Donc quelqu'un qui n'a tapé que des espaces est écarté.
if à la fin versus if au début
Deux choses différentes, et la position est la différence :
marks = [72, 33, 90]
labels = ["pass" if m >= 40 else "fail" for m in marks]
print(labels)['pass', 'fail', 'pass']Un if à la fin filtre — certaines choses n'atteignent jamais le résultat, donc le résultat peut être plus court.
Un if ... else au début choisit — chaque chose produit quelque chose, donc la longueur ne change pas. C'est la condition sur une ligne du chapitre quinze, à l'intérieur d'une compréhension.
La longueur change-t-elle ? Cette question est le moyen le plus simple de distinguer les deux.
Dictionnaires et ensembles aussi
La même forme, des accolades différentes :
marks = {"rafi": 72, "ahmed": 45, "bilal": 90}
passed = {name: mark for name, mark in marks.items() if mark >= 50}
print(passed)
doubled = {name: mark * 2 for name, mark in marks.items()}
print(doubled){'rafi': 72, 'bilal': 90}
{'rafi': 144, 'ahmed': 90, 'bilal': 180}Des accolades et un deux-points font une compréhension de dictionnaire. marks.items() renvoie un tuple à chaque tour et name, mark le dépaquète, exactement comme dans une boucle.
Les mêmes accolades sans deux-points font un ensemble :
orders = [
{"item": "pen"},
{"item": "bag"},
{"item": "pen"},
]
items = {order["item"] for order in orders}
print(sorted(items))['bag', 'pen']La question « combien de différents » du chapitre seize en une ligne. pen arrive deux fois et n'apparaît qu'une fois.
Nombres en chaînes — que vous avez déjà vus
Cette ligne est apparue au chapitre treize avec la promesse que l'explication viendrait :
marks = [72, 45, 90]
print(", ".join(str(m) for m in marks))
print([str(m) for m in marks])72, 45, 90
['72', '45', '90']Maintenant, elle peut être lue en entier. str(m) for m in marks est une compréhension sans les crochets — cette forme s'appelle une expression génératrice, et au lieu de construire toute la liste d'un coup, elle donne à join un élément à la fois. Les crochets peuvent être omis quand c'est le seul argument d'une fonction, et sur de grosses données cela économise de la mémoire.
Quand ne pas en écrire une
Savoir écrire une compréhension ne veut pas dire qu'il faut le faire. Regardez ceci :
rows = [["1", "2"], ["3", "x"]]
numbers = [int(c) for row in rows for c in row if c.isdigit()]
print(numbers)[1, 2, 3]La ligne est correcte. Mais elle a deux for et un if, et il faut se souvenir de l'ordre : les for se lisent du plus extérieur au plus intérieur, comme s'écriraient des boucles imbriquées. Six mois plus tard, cette ligne est difficile à lire.
En boucle :
rows = [["1", "2"], ["3", "x"]]
numbers = []
for row in rows:
for c in row:
if c.isdigit():
numbers.append(int(c))
print(numbers)[1, 2, 3]Plus de lignes, moins de réflexion. Le but du code n'est pas d'être court mais d'être clair — et la plupart du temps le raccourcir le rend plus clair, mais pas toujours.
Comme règle de travail : un for et au plus un if — une compréhension. Plus que ça — une boucle. Et ne mettez jamais de print ou d'append dans une compréhension ; c'est un outil pour construire une valeur, pas pour faire du travail.
Un exemple complet
views.py :
# One list of records in, three different views out
orders = [
{"customer": "rafi", "item": "pen", "quantity": 3, "price": 15.0},
{"customer": "ahmed", "item": "bag", "quantity": 1, "price": 850.0},
{"customer": "rafi", "item": "ink", "quantity": 2, "price": 120.0},
{"customer": "dia", "item": "pen", "quantity": 5, "price": 15.0},
]
totals = [order["quantity"] * order["price"] for order in orders]
print("Totals :", totals)
big = [order["item"] for order in orders if order["quantity"] * order["price"] > 100]
print("Over 100 :", big)
items = {order["item"] for order in orders}
print("Distinct :", sorted(items))
by_item = {order["item"]: order["price"] for order in orders}
print("Price list:", by_item)
print("Grand :", sum(totals))Totals : [45.0, 850.0, 240.0, 75.0]
Over 100 : ['bag', 'ink']
Distinct : ['bag', 'ink', 'pen']
Price list: {'pen': 15.0, 'bag': 850.0, 'ink': 120.0}
Grand : 1210.0Les mêmes données que le chapitre dix-sept, avec la plupart de ce qui était une boucle là maintenant en une ligne.
Trois choses méritent d'être notées.
totals et sum(totals) sont des lignes séparées. Le total aurait pu être sum(order["quantity"] * order["price"] for order in orders), mais alors les valeurs individuelles de ligne n'existeraient pas. Un nom intermédiaire vaut souvent plus qu'une astuce en une ligne.
Le même calcul apparaît deux fois dans big — une fois dans la condition, et il serait à nouveau nécessaire dans le résultat. Ici le résultat ne prend que le nom, donc rien n'est perdu ; mais quand vous avez besoin des deux, c'est une bonne raison de quitter la compréhension et d'écrire une boucle, où la valeur peut être calculée une fois et recevoir un nom.
by_item a perdu un enregistrement. Quatre commandes, trois paires dans le résultat — pen apparaît deux fois, et par la règle du chapitre quinze la dernière a remplacé la première silencieusement. Ici les deux pen ont le même prix donc ce n'est pas grave ; avec des prix différents ce serait silencieusement faux. Une compréhension ne crée pas ce problème, mais sa brièveté laisse moins de chances de le repérer.
En cas de problème
SyntaxError en mettant if et else à la fin [m for m in marks if m >= 40 else 0] n'est pas valide. Le if de filtrage va à la fin et n'a pas de else ; le if ... else de choix va au début : [m if m >= 40 else 0 for m in marks].
Le résultat ne contient que des None Quelque chose à l'intérieur de la compréhension ne renvoie rien — comme [items.append(x) for x in other]. append renvoie None, donc le résultat est une liste de None. Une compréhension construit des valeurs ; pour faire du travail, écrivez une boucle ordinaire.
NameError — la variable de la compréhension a disparu à l'extérieur Après [m * 2 for m in marks] vous ne pouvez pas utiliser m. Au chapitre onze, la variable d'une boucle for survivait à la boucle ; celle d'une compréhension non — elle ne vit qu'à l'intérieur.
Une compréhension de dictionnaire a produit moins de paires que prévu La même clé a été produite plus d'une fois et la dernière a remplacé la première. Comparez avec len().
Je ne peux pas lire la ligne C'est raison suffisante. Décomposez-la en boucle — la différence de vitesse importe rarement pour les débutants, et la prochaine personne à la lire, probablement vous, sera reconnaissante.
Étape 4 sur 6 — Prédire
Vérifiez votre compréhension
Il y a un if à la fin. Que s'affiche ?
marks = [72, 45, 90, 33]
passed = [mark for mark in marks if mark >= 40]
print(passed)- A[72, 45, 90, 33]
- B[True, True, True, False]
- C[33]
- D[72, 45, 90]
Cette fois le if est au début, avec un else. Que s'affiche ?
marks = [72, 33, 90]
labels = ["pass" if m >= 40 else "fail" for m in marks]
print(labels)- A['pass', 'fail', 'pass']
- B['pass', 'pass']
- C[72, 90]
- D['pass', 'fail']
append est appelé dans une compréhension. Que disent les deux lignes ?
source = ["pen", "bag"]
target = []
result = [target.append(item) for item in source]
print(target)
print(result)- A['pen', 'bag'] [None, None]
- BUn `TypeError`
- C['pen', 'bag'] ['pen', 'bag']
- D[] ['pen', 'bag']
Répondre nécessite un compte
Connectez-vous pour vérifier vos réponses
Les questions sont ci-dessus, et les résoudre de tête est ce qui compte vraiment. Connectez-vous pour voir les réponses, les explications et les indices à trois niveaux.
À vous de jouer
Écrivez un fichier appelé clean.py avec une liste de noms en désordre — certains avec des espaces en trop, certains en majuscules, et au moins un qui est entièrement vide.
En utilisant des compréhensions, construisez :
- Une liste de noms nettoyés — en minuscules, en écartant les vides
- Une liste de la longueur de chaque nom
- Une liste des noms plus longs que cinq caractères
- Un dictionnaire
{name: length} - Un ensemble des premières lettres
Puis deux expériences :
- Réécrivez le numéro 1 comme une boucle
forordinaire, et vérifiez que les deux résultats sont égaux avec== - Écrivez une compréhension avec deux
foret unif, puis décomposez-la en boucle. Laquelle est plus facile à lire ?
Cette dernière question n'a pas de réponse « juste », et c'est le but. Apprendre à se la poser est la vraie leçon de ce chapitre.
Étape 6 sur 6
Défi — le quiz du chapitre
Dix questions, de la plus facile à la plus difficile. Les dernières sont difficiles exprès.
Connectez-vous pour faire le quiz