Question 1Qu'affiche ce code ?stock = 100
def f():
stock = 50
f()
print(stock)
Fonctions internes et clôtures — maîtriser la portée avec global et nonlocal
Apprends la portée des variables en Python avec les fonctions internes, les clôtures et les mots-clés global et nonlocal.
Dans l'article précédent, tu as vu comment se comportent les arguments et les valeurs de retour. Cette fois, tu vas creuser quelques sujets supplémentaires liés aux fonctions : comment Python sépare les variables à l'intérieur et à l'extérieur d'une fonction (portée), comment définir des fonctions à l'intérieur d'autres fonctions (fonctions internes), et comment retourner une fonction qui se souvient des valeurs externes (une clôture). Au passage, tu verras quand utiliser global et nonlocal pour réécrire une variable englobante depuis l'intérieur d'une fonction.
Variables globales et locales — des portées séparées
Les variables définies hors de toute fonction sont des variables globales ; celles définies à l'intérieur d'une fonction sont des variables locales. Tu peux lire une globale depuis l'intérieur d'une fonction, mais si tu affectes le même nom avec = valeur à l'intérieur de la fonction, Python crée une nouvelle variable locale — une chose distincte de la variable externe.
Si tu vérifies l'adresse mémoire d'une variable avec id(), tu verras que les versions externe et interne pointent vers des emplacements différents.
stock = 100 # variable globale
def show_stock():
print(f"intérieur : {stock}") # 100 — lit la valeur externe
def try_change():
stock = 50 # crée une toute nouvelle variable locale
print(f"intérieur : {stock}") # 50
show_stock() # intérieur : 100
try_change() # intérieur : 50
print(f"extérieur : {stock}") # extérieur : 100 ← l'externe est inchangée
- stock = 100 — variable globale
- Les fonctions ne peuvent que la lire depuis l'intérieur
print(stock)→ lit la valeur externe 100- Ne crée aucune variable locale
stock = 50— crée une nouvelle locale dans la fonction- N'affecte pas le
stockexterne
Réécrire une variable externe avec le mot-clé global
Quand tu veux vraiment réécrire une globale externe depuis l'intérieur d'une fonction, déclare global nom_de_variable en tête de la fonction. Cela dit à Python : « c'est cette globale externe, pas une nouvelle locale. » Sans cela, une affectation comme count += 1 mélange une lecture et une écriture du même nom et tu obtiens UnboundLocalError (« local variable referenced before assignment »).
visit_count = 0
# × Sans global → UnboundLocalError
# def increment():
# visit_count += 1
# increment() ← UnboundLocalError
# ○ global te permet de réécrire la globale externe
def increment():
global visit_count
visit_count += 1
increment()
increment()
increment()
print(visit_count) # 3
Garde global au minimum
global permet à une fonction de réécrire silencieusement l'état externe, ce qui rend, à grande échelle, une seule mauvaise écriture située 100 lignes plus loin étonnamment difficile à pister.
Par défaut, retourne une valeur avec return et affecte-la côté appelant, et tourne-toi vers les classes (vues plus tard) quand tu as vraiment besoin d'un comportement avec état.
Comment fonctions et variables vivent en mémoire — noms et choses
En interne, Python tient un espace de noms — une table de correspondance « nom → chose ». x = 5 dit : « crée l'entier 5 en mémoire et fais que le nom x pointe dessus. »
def f(): ... fonctionne pareillement : il construit un objet fonction (le corps de la fonction) en mémoire et fait que le nom f pointe dessus. Il y a exactement une de ces tables par programme — l'espace de noms global — créée au chargement du module et conservée jusqu'à la fin du programme.
- x = 5 — le nom
xpointe vers l'entier 5 - def f — le nom
fpointe vers un objet fonction - Reste vivant jusqu'à la fin du programme
- Contient les arguments et les locales
- Entièrement jeté au return
- Complètement séparé du premier
- Les locales sont indépendantes
def une fois et la fonction est enregistrée dans l'espace de noms global, où elle reste jusqu'à la fin. Chaque appel, en revanche, construit son propre frame local et le jette au retour. Les fonctions internes, clôtures et nonlocal qui suivent reposent tous sur cette structure de frames.Fonctions internes — définir des fonctions dans des fonctions
Empile un autre def à l'intérieur d'une fonction et tu obtiens une fonction interne — une fonction qui n'existe que pour être utilisée à l'intérieur de la fonction externe. C'est un excellent moyen de nommer un bloc de logique significatif au sein d'une longue fonction, pour que le corps se lise comme une suite d'étapes claires.
Une fonction interne n'étant pas accessible depuis l'extérieur, elle convient aussi bien pour cacher des helpers que tu ne veux pas exposer.
- Appeler
validatedepuis ici → NameError - N'existe pas à l'extérieur
- Contient les arguments
name/age - Peut appeler
validatedepuis l'intérieur
- Lit les
name/ageexternes - Pas exposé à l'extérieur
def process_user(name, age):
def validate():
if not name or not isinstance(age, int) or age < 0:
raise ValueError("Entrée invalide")
validate() # appelle la fonction interne
print(f"Traité : {name} ({age})")
process_user("Alice", 25)
# Traité : Alice (25)
# validate n'est pas accessible depuis l'extérieur de process_user
# validate() ← NameError
Idéal pour découper une longue fonction
Dès qu'une fonction dépasse 50 ou 100 lignes, la découper en fonctions internes nommées d'après chaque bloc significatif — process_name() / process_age() etc. — transforme la fonction externe en une liste lisible d'étapes. Si tu veux un jour en réutiliser une à l'extérieur, promouvoir une fonction interne en fonction normale est trivial.
Clôtures — des fonctions qui se souviennent de valeurs externes
Les fonctions internes peuvent lire les arguments et les locales de la fonction externe. Pousse l'idée d'un cran — fais que la fonction externe return la fonction interne — et tu as construit une fonction qui continue de fonctionner avec les valeurs externes intégrées. C'est une clôture.
C'est une manière propre de produire en série des fonctions similaires qui ne diffèrent que par un réglage, comme « une fonction qui triple » et « une fonction qui quintuple ». Prends le réglage (factor) en argument externe et référence-le depuis la fonction interne : make_multiplier(3) retourne « une fonction multiply qui se souvient de 3 », et make_multiplier(5) retourne « une fonction multiply qui se souvient de 5 ».
def make_multiplier(factor):
def multiply(x):
return x * factor # référence le factor externe
return multiply
times3 = make_multiplier(3) # fonction qui se souvient de factor=3
times5 = make_multiplier(5) # une fonction distincte qui se souvient de factor=5
print(times3(10)) # 30
print(times5(10)) # 50
print(times3(7)) # 21
Les clôtures distribuent des fonctions « préconfigurées »
Quand tu veux créer plein de calculs similaires qui ne diffèrent que par un réglage — « TVA 10 % » contre « TVA 8 % » etc. —, les clôtures brillent. Au lieu de passer le réglage à chaque appel, tu distribues une seule fonction préconfigurée, et le code appelant reste bien plus propre.
nonlocal — réécrire la variable d'une fonction englobante
Les clôtures peuvent lire la variable externe sans souci, mais comme avec global, essayer de la réécrire via count += 1 plante avec UnboundLocalError. Le mot-clé qui autorise cette réécriture est nonlocal. Là où global cible le niveau du module, nonlocal cible la locale de la fonction immédiatement englobante.
Encapsule l'état dans un objet fonction
nonlocal est la manière canonique d'encapsuler un compteur qui grandit à chaque appel dans une fonction.
Contrairement à global, l'état reste enfermé dans un objet fonction précis, donc les effets de bord ne se propagent pas, et tu obtiens un état plus sûr qu'en passant par une globale.
def create_counter():
x = 0
def increment():
nonlocal x # déclare qu'on met à jour le x de create_counter
x += 1
return x
return increment
counter = create_counter()
print(counter()) # 1
print(counter()) # 2
print(counter()) # 3
# Un second compteur a son propre x indépendant
counter2 = create_counter()
print(counter2()) # 1 — sans rapport avec counter
counter = create_counter()— reçoitincrement- Le code extérieur ne peut pas toucher
xdirectement
- x = 0 — compteur, créé une seule fois
- Ce que
incrementréférence vianonlocal
nonlocal x— pointe vers lexexternex += 1met à jour le x externe
Vérification des connaissances
Répondez à chaque question une par une.
Question 2Pourquoi ce code lève-t-il une erreur ? Choisis la meilleure explication.count = 0
def inc():
count += 1
inc()
Question 3Qu'affiche print(times3(10)) ?def make_multiplier(factor):
def multiply(x):
return x * factor
return multiply
times3 = make_multiplier(3)