Aller au contenu

Utiliser des collections et des outils fonctionnels

collections : conteneurs spécialisés

Classe Usage
Counter Compter les occurrences.
defaultdict Produire une valeur initiale pour une clé absente.
deque Ajouter ou retirer efficacement aux deux extrémités.
ChainMap Présenter plusieurs dictionnaires comme une recherche commune.
namedtuple Créer des tuples dont les champs ont un nom.
from collections import Counter, defaultdict, deque

counts = Counter(["Python", "C#", "Python"])
print(counts.most_common(1))  # [('Python', 2)]

groups = defaultdict(list)
for name, category in [("Clavier", "Matériel"), ("Souris", "Matériel")]:
    groups[category].append(name)
print(groups["Matériel"])  # ['Clavier', 'Souris']

queue = deque(["A", "B"])
queue.append("C")
print(queue.popleft())  # A

Pour communiquer entre threads, le module queue fournit des conteneurs avec des mécanismes de synchronisation adaptés.

Parallèle C# et .NET

string[] languages = { "Python", "C#", "Python" };
var counts = languages.GroupBy(x => x).ToDictionary(g => g.Key, g => g.Count());
Console.WriteLine(counts["Python"]); // 2
var groups = new Dictionary<string, List<string>>();

foreach (string name in new[] { "Clavier", "Souris" })
{
    if (!groups.TryGetValue("Matériel", out var items))
        groups["Matériel"] = items = new List<string>();
    items.Add(name);
}

var queue = new Queue<string>(new[] { "A", "B" });
queue.Enqueue("C");

Console.WriteLine(queue.Dequeue()); // A

GroupBy() et les dictionnaires couvrent le comptage ; TryGetValue() permet une initialisation explicite. Queue<T> couvre seulement le comportement FIFO du deque montré ; LinkedList<T> permet des opérations aux deux extrémités avec d’autres caractéristiques. Un tuple nommé ou un record se rapproche de namedtuple. Les collections ordinaires ne sont pas automatiquement synchronisées.

itertools : combiner des itérations

Ces fonctions produisent généralement des itérateurs, ce qui évite de matérialiser immédiatement tous les résultats.

Fonction Usage
chain() Enchaîner des itérables.
islice() Extraire une portion d'itérateur.
count() / repeat() / cycle() Générer des suites, parfois infinies.
product() Produit cartésien.
combinations() / permutations() Combinaisons / permutations.
accumulate() Calculs cumulés.
groupby() Regrouper des éléments consécutifs selon une clé.
from itertools import chain, combinations, accumulate

print(list(chain([1, 2], [3, 4])))  # [1, 2, 3, 4]
print(list(combinations(["A", "B", "C"], 2)))
# [('A', 'B'), ('A', 'C'), ('B', 'C')]
print(list(accumulate([10, 20, 30])))  # [10, 30, 60]

groupby() regroupe uniquement les clés adjacentes : un tri préalable est souvent nécessaire. Ne convertissons pas un itérateur infini en liste.

Parallèle C# et .NET

Console.WriteLine(string.Join(", ", new[] { 1, 2 }.Concat(new[] { 3, 4 })));
string[] letters = { "A", "B", "C" };
var pairs = letters.SelectMany((a, i) => letters.Skip(i + 1).Select(b => (a, b)));
foreach (var pair in pairs) Console.WriteLine(pair);

static IEnumerable<int> CumulativeSum(IEnumerable<int> values)
{
    int total = 0;
    foreach (int value in values) { total += value; yield return total; }
}
Console.WriteLine(string.Join(", ", CumulativeSum(new[] { 10, 20, 30 })));

Concat() correspond à chain() et Skip() / Take() à des portions d’itération. SelectMany() exprime les produits et combinaisons simples ; yield return construit des itérateurs spécialisés. Différence majeure : LINQ GroupBy() rassemble les clés égales sur toute la source ; itertools.groupby() regroupe uniquement des éléments adjacents. Une requête LINQ est souvent réénumérable ; un itérateur Python est consommable.

functools : adapter et mémoriser des fonctions

Élément Usage
partial() Fixer certains arguments d'une fonction.
cache / lru_cache Mémoriser les résultats selon les arguments.
wraps() Préserver les métadonnées lors de l'écriture d'un décorateur.
reduce() Réduire un itérable par application successive d'une fonction.
from functools import partial, lru_cache

def calculate_total(price, tax_rate):
    return price * (1 + tax_rate)

standard_total = partial(calculate_total, tax_rate=0.20)
print(standard_total(100))  # 120.0

@lru_cache(maxsize=128)
def square(number):
    return number * number

print(square(12))  # 144

La mémorisation exige des arguments hachables et convient lorsque le résultat reste valide pour les mêmes arguments. cache n'impose pas de limite de taille.

Parallèle C# et .NET

static double CalculateTotal(double price, double taxRate) => price * (1 + taxRate);
Func<double, double> standardTotal = price => CalculateTotal(price, 0.20);
Console.WriteLine(standardTotal(100)); // 120
var cache = new Dictionary<int, int>();
int Square(int number)
{
    if (!cache.TryGetValue(number, out int result))
        cache[number] = result = number * number;
    return result;
}
Console.WriteLine(Square(12)); // 144

Une fermeture ou une lambda exprime le besoin de partial(). Le dictionnaire montre une mémorisation simple, sans limite de taille, politique LRU ni synchronisation : ce n’est pas un remplacement complet de lru_cache. LINQ Aggregate() se rapproche de reduce(). Les décorateurs de fonctions Python n’ont pas de mécanisme C# strictement identique ; les attributs C# n’enveloppent pas automatiquement une fonction.

copy : copier des structures

copy() effectue une copie superficielle ; deepcopy() copie récursivement les objets concernés en tenant compte des références déjà traitées.

import copy

original = [[1, 2], [3, 4]]
shallow = copy.copy(original)
deep = copy.deepcopy(original)

original[0].append(9)
print(shallow[0])  # [1, 2, 9]
print(deep[0])     # [1, 2]

Une copie profonde n'est pas automatiquement préférable : elle peut coûter cher et ne duplique pas arbitrairement des ressources externes comme des connexions.

Parallèle C# et .NET

var original = new List<List<int>> { new() { 1, 2 }, new() { 3, 4 } };
var shallow = new List<List<int>>(original);
var deepForThisShape = original.Select(row => new List<int>(row)).ToList();
original[0].Add(9);
Console.WriteLine(string.Join(", ", shallow[0]));          // 1, 2, 9
Console.WriteLine(string.Join(", ", deepForThisShape[0])); // 1, 2

Le constructeur de liste effectue une copie superficielle. La projection copie les lignes de cette structure précise, pas un graphe d’objets arbitraire. .NET ne fournit pas un deepcopy() universel comparable. MemberwiseClone() est superficiel ; ICloneable ne précise pas à lui seul la profondeur du clonage.