Portée des fonctions et variables en Python

Ordre de résolution des variables

Python recherche les variables dans l'ordre suivant : variables locales du bloc courant → variables des blocs englobants → variables globales du module courant → variables intégrées à Python.

1. Le mot-clé global

Le mot-clé global permet d'utiliser une variable globale au sein d'une fonction. Si la variable n'est que consultée, la déclaration n'est pas nécessaire. Par contre, pour la modifier depuis l'intérieur d'une fonction, il faut obligatoirement la déclarer avec global. Sans cela, Python lèvera une exception UnboundLocalError.

valeur_compteur = 1

def incrementer():
    global valeur_compteur
    valeur_compteur += 1
    print(valeur_compteur)

incrementer()

2. Le mot-clé nonlocal

Ce mot-clé s'applique dans le contexte des fonctions imbriquées (closures). Lorsqu'une fonctino interne souhaite modifier une variable de la fonction englobante, il faut la déclarer avec nonlocal.

nonlocal <nom_de_variable>

L'instruction nonlocal effectue une recherche dans la pile d'appels pour trouver la définition de la variable dans la fonction englobante immédiate.

def definir_fonction_ext():
    compteur = 0
    def fonction_int():
        nonlocal compteur
        compteur += 1
        print(compteur)
    return fonction_int

appel = definir_fonction_ext()
appel()

3. Les décorateurs

Un décorateur prend une fonction en argument, lui ajoute un comportement, et retourne la fonction modifiée. C'est une application particulière des closures.

def mon_decorateur(fonction):
    def enveloppe():
        print("Avant l'appel")
        fonction()
        print("Après l'appel")
    return enveloppe

@mon_decorateur
def dire_bonjour():
    print("Bonjour !")

dire_bonjour()

Mécanisme d'exécution :
Lorsque @mon_decorateur est appliqué à dire_bonjour, Python exécute mon_decorateur(dire_bonjour), qui retourne la fonction enveloppe. L'appel à dire_bonjour() exécute alors enveloppe().

4. Les closures

Une closure est une fonction qui se sovuient des variables de l'environnement dans lequel elle a été créée, même si cet environnement a cessé d'exister.

def createur_message(prefixe):
    def fonction_message(sujet):
        print(prefixe + " : " + sujet)
    return fonction_message

generateur_info = createur_message("INFO")
generateur_info("Démarrage du service")
# Sortie : INFO : Démarrage du service

Dans cet exemple, fonction_message est une closure. La variable prefixe est une variable libre capturée par la closure. Même après que l'appel à createur_message soit terminé, prefixe demeure accessible via la closure.

5. Liaison tardive

La portée des fonctions en Python est déterminée de manière statique par le code, mais leur utilisation est dynamique, décidée à l'exécution.

Cela peut poser un problème dans les closures, comme le montre cet exemple :

liste_fonctions = [lambda x: x * index for index in range(4)]
print(liste_fonctions[0](1))  # Affiche 3, et non 0 comme attendu

Ce comportement s'explique par le fait que la variable index n'est recherchée qu'au moment de l'appel de la lambda. La boucle étant terminée, index vaut sa dernière valeur (3).

Pour obtenir le résultat souhaité, on utilise une closure pour capturer la valeur courante à chaque itération :

def creer_multiplicateur(facteur):
    return lambda x: x * facteur

liste_fonctions = [creer_multiplicateur(i) for i in range(4)]
print(liste_fonctions[0](1))  # Affiche 0

Étiquettes: Python portée global nonlocal closures

Publié le 21 juin à 06h08