Retour au cours

portée des variables et closures

dans cette leçon, vous allez comprendre et quand vos variables existent en python, comment les fonctions imbriquées interagissent avec les variables extérieures, et comment utiliser (avec précaution) global et nonlocal.

objectifs

  • connaître les types de portée : locale, englobante, globale, intégrée (LEGB)
  • comprendre l’ombre (shadowing) de variables
  • utiliser nonlocal pour modifier une variable dans une fonction englobante
  • créer et utiliser des closures

règle LEGB

ordre de recherche d’un nom :

  1. Local : variables définies dans la fonction en cours
  2. Enclosing : variables dans les fonctions englobantes (closures)
  3. Global : variables définies au niveau du module
  4. Built-in : noms fournis par python (len, print, etc.)
x = "global"

def outer():
    x = "enclosing"
    def inner():
        x = "local"
        print(x)
    inner()

outer()  # affiche "local"

shadowing : masquer un nom

si une variable locale porte le même nom qu’une variable globale, la globale est masquée.

val = 42

def test():
    val = 99
    print(val)

test()      # 99
print(val)  # 42

global : modifier une variable globale

count = 0

def increment():
    global count
    count += 1

increment()
print(count)

⚠ évitez de modifier des globales, cela rend le code moins prévisible.

nonlocal : modifier une variable englobante non globale

def outer():
    x = 0
    def inner():
        nonlocal x
        x += 1
        return x
    return inner

compteur = outer()
print(compteur())  # 1
print(compteur())  # 2

closures : fonctions qui capturent un contexte

une closure se produit lorsqu’une fonction interne capture des variables de son environnement et les “mémorise” même après la fin de l’exécution de la fonction englobante.

def make_multiplier(facteur):
    def multiplier(n):
        return n * facteur
    return multiplier

double = make_multiplier(2)
triple = make_multiplier(3)

print(double(5))  # 10
print(triple(5))  # 15

ici, facteur est conservé dans la closure de multiplier.

inspecter une closure

print(double.__closure__)       # tuple de cellules capturées
print(double.__closure__[0].cell_contents)  # 2

bonnes pratiques

  • évitez global sauf cas très simples (ex: compteur rapide en script)
  • utilisez nonlocal seulement si la variable doit vivre dans l’enclos fonctionnel
  • préférez retourner une valeur plutôt que modifier une variable externe
  • closures : utiles pour fabriquer des fonctions spécialisées (fabrication de callbacks, paramètres pré-configurés)

mini exemples exécutables

# compteur avec closure
def make_counter():
    count = 0
    def inc():
        nonlocal count
        count += 1
        return count
    return inc

c1 = make_counter()
print(c1(), c1(), c1())  # 1 2 3
# générateur de filtres
def make_filter(min_value):
    def filtrer(seq):
        return [x for x in seq if x >= min_value]
    return filtrer

filtre10 = make_filter(10)
print(filtre10([5, 10, 15]))
# usage global (à éviter en vrai)
flag = False
def toggle():
    global flag
    flag = not flag

toggle()
print(flag)

pièges courants

  • oublier nonlocal → création d’une variable locale au lieu de modifier celle de l’enclos
  • réutiliser des noms built-in (list, id, type) → masque la fonction intégrée
  • abuser de global → rend le code difficile à tester
  • mutable dans closure : toutes les fonctions créées partagent la même référence

exercices

  1. make_adder(n) → retourne une fonction qui ajoute n à son argument.
  2. make_accumulator() → retourne une fonction qui additionne les valeurs qu’on lui donne et garde la somme en mémoire.
  3. réécrire un compteur avec global au lieu de nonlocal (observer la différence).
  4. créer make_power(exp) qui retourne une fonction élevant un nombre à exp.
  5. expliquer pourquoi la variable capturée dans une closure conserve sa valeur même après la fin de la fonction englobante.