Les compréhensions
Introduction
En Python, les compréhensions permettent de créer une collection à partir d’une structure de données ou d'objets itérable, en regroupant le parcours des éléments, leur transformation et leur éventuel filtrage et ordonnancement dans une même expression.
Pour un développeur C#, ces opérations peuvent être rapprochées de LINQ : Select pour transformer les éléments, Where pour les filtrer et SelectMany pour parcourir des collections imbriquées. Les exemples C# suivants impliquent que l’espace de noms System.Linq est importé.
Les collections pouvant être parcourues
Une compréhension peut parcourir tout objet itérable, notamment :
- Une liste.
- Un tuple.
- Un ensemble.
- Un dictionnaire.
- Une chaîne de caractères.
- Une collection d’objets.
- Une séquence produite par
range. - Un générateur.
Cette possibilité se rapproche de LINQ to Objects, dont les opérateurs s'appliquent à des sources implémentant l'interface IEnumerable<T>.
Le type de la source ne détermine pas celui du résultat. Par exemple, nous pouvons parcourir un tuple et produire une liste :
numbers = (1, 2, 3)
doubled_numbers = [number * 2 for number in numbers]
print(doubled_numbers)
# [2, 4, 6]
Les compréhensions de liste
Du traitement itératif à une compréhension
Pour créer une liste contenant le double de chaque nombre, nous pouvons écrire une boucle.
En Python :
numbers = [12, 7, 25, 8, 14, 3, 19]
doubled_numbers = []
for number in numbers:
doubled_numbers.append(number * 2)
En C# :
var numbers = new List<int> { 12, 7, 25, 8, 14, 3, 19 };
var doubledNumbers = new List<int>();
foreach (int number in numbers)
{
doubledNumbers.Add(number * 2);
}
En Python, une compréhension permet d’obtenir la liste souhaitée :
doubled_numbers = [number * 2 for number in numbers]
print(doubled_numbers)
# [24, 14, 50, 16, 28, 6, 38]
En C#, nous pouvons utiliser Select, puis ToList :
Syntaxe générale
Chaque partie joue un rôle précis :
| Élément | Rôle |
|---|---|
iterable |
Source à parcourir |
element |
Variable recevant chaque élément |
expression |
Valeur à ajouter à la nouvelle liste |
Dans l’expression suivante :
La partie for number in numbers parcourt la source. L’expression number * 2 transforme chaque élément, comme la lambda transmise à la méthode Select.
Comprendre la répétition de la variable
Considérons cette compréhension :
Les deux occurrences de number ont des rôles différents :
- Dans
for number in numbers,numberest la variable de parcours. - Le premier
numberdésigne la valeur à ajouter à la nouvelle liste.
En C#, une projection identique s’écrit :
Pour simplement copier cette liste, numbers.ToList() est suffisant. Dans les deux langages, la création d’une nouvelle liste ne clone pas les objets qu’elle contient.
Filtrer les éléments
Une condition placée après le for permet de conserver uniquement certains éléments.
Syntaxe
Exemple
Conservons uniquement les nombres pairs.
En Python :
even_numbers = [
number
for number in numbers
if number % 2 == 0
]
print(even_numbers)
# [12, 8, 14]
En C# :
La compréhension Python se lit ainsi :
Pour chaque nombre de
numbers, s’il est pair, ajouter ce nombre à la nouvelle liste.
L’opérateur % calcule le reste d’une division. Un entier est pair lorsque le reste de sa division par 2 vaut zéro.
Combiner filtrage et transformation
Conservons les nombres pairs, puis calculons leur double.
En Python :
doubled_even_numbers = [
number * 2
for number in numbers
if number % 2 == 0
]
print(doubled_even_numbers)
# [24, 16, 28]
En C# :
var doubledEvenNumbers = numbers
.Where(number => number % 2 == 0)
.Select(number => number * 2)
.ToList();
La condition est évaluée avant l’expression produisant le résultat.
Plusieurs conditions peuvent être combinées avec and et or, qui correspondent ici aux opérateurs C# && et || :
Choisir une valeur selon une condition
Une expression conditionnelle permet de produire une valeur différente selon une condition, sans exclure d’éléments.
En Python :
En C# :
L’expression Python :
correspond à l’expression conditionnelle C# :
La position du if dans la compréhension permet de distinguer deux usages :
| Syntaxe Python | Usage |
|---|---|
[expression for x in source if condition] |
Filtrer les éléments |
[a if condition else b for x in source] |
Choisir la valeur "produite" pour chaque élément |
Parcourir une collection d’objets
Les compréhensions permettent d’accéder aux attributs et aux méthodes des objets parcourus.
En Python :
class Product:
def __init__(self, name: str, price: float):
self.name = name
self.price = price
products = [
Product("Clavier", 49.90),
Product("Souris", 24.90),
Product("Écran", 199.00)
]
affordable_names = [
product.name
for product in products
if product.price < 100
]
print(affordable_names)
# ['Clavier', 'Souris']
En C#, avec une classe Product exposant les propriétés Name et Price :
var affordableNames = products
.Where(product => product.Price < 100)
.Select(product => product.Name)
.ToList();
Pour conserver les objets eux-mêmes, nous utilisons directement la variable de parcours :
La nouvelle liste contient les objets existants, sans les cloner.
Les compréhensions d’ensemble
Un ensemble contient des éléments uniques. Une compréhension d’ensemble utilise des accolades :
En Python :
numbers = [1, -2, 2, -3, 3]
absolute_numbers = {abs(number) for number in numbers}
print(sorted(absolute_numbers))
# [1, 2, 3]
En C# :
var numbers = new List<int> { 1, -2, 2, -3, 3 };
var absoluteNumbers = numbers
.Select(number => Math.Abs(number))
.ToHashSet();
Les valeurs absolues sont calculées, puis les doublons sont éliminés.
Un ensemble Python ne garantit pas l’ordre d’itération. Dans l’exemple, la fonction sorted est utilisée pour obtenir un affichage ordonné.
Les compréhensions de dictionnaire
Syntaxe
Une compréhension de dictionnaire produit des couples clé-valeur :
Créer des associations
En Python :
squares = {
number: number ** 2
for number in range(1, 6)
}
print(squares)
# {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
En C# :
La fonction range(1, 6) produit les entiers de 1 à 5, la borne supérieure étant exclue. Enumerable.Range(1, 5) reçoit une valeur de départ et un nombre d’éléments.
Transformer un dictionnaire existant
En Python, items() permet de parcourir les couples clé-valeur :
prices = {
"Clavier": 50,
"Souris": 25,
"Écran": 200
}
discounted_prices = {
name: price * 0.9
for name, price in prices.items()
if price < 100
}
print(discounted_prices)
# {'Clavier': 45.0, 'Souris': 22.5}
En C# :
var discountedPrices = prices
.Where(entry => entry.Value < 100)
.ToDictionary(
entry => entry.Key,
entry => entry.Value * 0.9);
Attention aux clés identiques
Une différence existe avec ToDictionary.
En Python, si plusieurs éléments produisent la même clé, la dernière valeur produite est conservée :
words = ["chat", "lion", "maison"]
words_by_length = {
len(word): word
for word in words
}
print(words_by_length)
# {4: 'lion', 6: 'maison'}
En C#, ToDictionary lève une exception lorsque plusieurs éléments produisent une même clé selon le comparateur utilisé.
Les parcours imbriqués
Une compréhension peut contenir plusieurs clauses for. Leur ordre correspond à celui des boucles imbriquées.
Créer des combinaisons
En Python :
colors = ["rouge", "bleu"]
sizes = ["S", "M"]
combinations = [
(color, size)
for color in colors
for size in sizes
]
print(combinations)
# [('rouge', 'S'), ('rouge', 'M'), ('bleu', 'S'), ('bleu', 'M')]
En C#, la syntaxe de requête LINQ présente une structure comparable :
Aplatir des collections
En Python :
matrix = [
[1, 2, 3],
[4, 5, 6]
]
flattened = [
number
for row in matrix
for number in row
]
print(flattened)
# [1, 2, 3, 4, 5, 6]
En C#, cette opération correspond à SelectMany :
Les expressions génératrices
Produire les valeurs à la demande
Une compréhension de liste construit immédiatement la liste obtenue.
Une expression génératrice utilise des parenthèses et produit les valeurs au fur et à mesure du parcours :
Ce fonctionnement se rapproche de l’exécution différée de la méthode Select en LINQ :
var squares = Enumerable.Range(1, 5)
.Select(number => number * number);
foreach (int square in squares)
{
Console.WriteLine(square);
}
Dans ces deux exemples, les carrés sont calculés pendant le parcours.
Comprendre les parcours successifs
Un générateur de données numériques en Python est consommé pendant son parcours. Une fois le traitement terminé, il ne produit plus de valeurs, même s'il est exécuté une seconde fois :
squares = (number ** 2 for number in range(1, 4))
print(list(squares))
# [1, 4, 9]
print(list(squares))
# []
Une requête LINQ to Object sur une source réitérable, comme une liste, peut généralement être parcourue plusieurs fois. Chaque parcours exécute de nouveau la requête.
Pour conserver les résultats en Python, nous pouvons les stocker dans une liste, et réutiliser cette liste dans les compréhensions.
Utiliser un générateur pour une agrégation
Une expression génératrice peut alimenter directement une fonction comme sum, sans créer de liste intermédiaire :
En C# :
Construire un tuple
Les parenthèses d’une expression génératrice ne créent pas un tuple et il n’existe pas de compréhension de tuple. Pour construire un tuple, nous utilisons la fonction tuple :
Un tuple Python est une séquence immuable. Il ne correspond pas à 100% aux tuples .NET, dont chaque position possède un type déterminé.