Maîtriser les fermetures, les expressions génératrices et les compréhensions en Python

Comprendre le concept de Fermeture (Closure)

Une fermeture, ou closure, survient lorsqu'une fonction interne accède aux variables définies dans la portée d'une fonction parente, même après que cette dernière a terminé son exécution. Contrairement à une fonction classique qui retourne une valeur simple, la fermeture renvoie l'objet fonction lui-même, encapsulant son environnement local.

Les principales caractéristiques d'une fermeture sont :

  • La capacité de mémoriser l'état des variables locales de la fonction parente.
  • Une amélioration de la modularité du code en évitant l'usage excessif de variables globales.
  • Une gestion spécifique de la mémoire, car les variables capturées ne sont pas détruites tant que la fermeture est référencée.
def generateur_prefixe(prefixe):
    # Cette variable est capturée par la fonction interne
    message_base = "Message: "
    
    def formater(texte):
        # Utilisation de nonlocal pour modifier une variable parente si nécessaire
        nonlocal message_base
        return f"{message_base} [{prefixe}] {texte}"
    
    return formater

# Instance de fermeture
log_info = generateur_prefixe("INFO")
print(log_info.__name__)  # Affiche: formater
print(log_info("Démarrage du système")) 
# Résultat: Message:  [INFO] Démarrage du système

Les fremetures sont particulièrement utiles dans la programmation asynchrone ou pour créer des décorateurs personnalisés, car elles permettent de "figer" des paramètres lors de la création de la fonction de rappel (callback).

Listes en compréhension vs Expressions génératrices

La distinction syntaxique majeure réside dans l'utilisation des crochets [] pour les listes et des parenthèses () pour les générateurs. Cependant, leur comportement interne, notamment concernant l'évaluation des variables, diffère radicalement.

Le piège de l'évaluation tardive (Late Binding)

Considérons l'exemple suivant utilisant une liste en compréhension contenant des fonctions anonymes (lambda) :

def creer_multiplicateurs():
    # Liste en compréhension
    return [lambda x: x * i for i in range(4)]

fonctions = creer_multiplicateurs()
resultats = [f(10) for f in fonctions]
print(resultats) 
# Résultat: [30, 30, 30, 30]

Dans ce cas, la boucle for à l'intérieur de la liste se termine avant que les fonctions lambda ne soient appelées. À la fin de la boucle, la variable i vaut 3. Toutes les fonctions lambda pointent vers la même référence de i, produisent ainsi le même résultat (10 * 3).

L'avantage de l'expression génératrice

Si l'on remplace les crochets par des parenthèses, le comportement change en raison de l'évaluation paresseuse (lazy evaluation) :

def generer_multiplicateurs():
    # Expression génératrice
    return (lambda x: x * i for i in range(4))

generateur = generer_multiplicateurs()
resultats = [f(10) for f in generateur]
print(resultats)
# Résultat: [0, 10, 20, 30]

Ici, le générateur produit chaque fonction lambda une par une. À chaque itération du consommateur (la liste finale), le générateur avance d'un pas, capturant la valeur actuelle de i au moment de la génération de l'élément.

Générateurs et le mot-clé yield

Un générateur est une fonction spéciale qui utilise yield au lieu de return. Lorsqu'elle est appelée, elle ne renvoie pas une valeur immédiatement mais un objet générateur qui peut être parcouru.

def suite_fibonacci(limite):
    a, b = 0, 1
    compteur = 0
    while compteur < limite:
        yield a
        a, b = b, a + b
        compteur += 1

# Consommation du générateur
for nombre in suite_fibonacci(6):
    print(nombre)

Itérateurs et Itérables

Pour bien structurer son code en Python, il est crucial de distinguer ces trois concepts :

  • Itérable : Un objet disposant d'une méthode __iter__ capable de retourner un itérateur (ex: listes, dictionnaires).
  • Itérateur : Un objet possédant les méthodes __iter__ (retourne soi-même) et __next__ (retourne l'élément suivant ou lève StopIteration).
  • Générateur : Un itérateur simplifié créé via une fonction avec yield ou une expression génératrice.

Étiquettes: Python closures Generators iterators FunctionalProgramming

Publié le 18 juillet à 17h22