Python & AI Tutorials Logo
Programmation Python

21. Portée des variables et résolution des noms

Lorsque vous créez une variable en Python, où « vit-elle » ? Une fonction peut-elle voir des variables créées en dehors d’elle ? Du code en dehors d’une fonction peut-il accéder à des variables créées à l’intérieur ? Ces questions concernent la portée (scope) — la région de votre programme où un nom est visible et peut être utilisé.

Comprendre la portée est crucial pour écrire des fonctions qui fonctionnent correctement et de manière prévisible. Sans ces connaissances, vous pourriez accidentellement créer des bugs où les variables n’ont pas les valeurs attendues, ou où les modifications des variables ne persistent pas comme prévu.

Dans ce chapitre, nous allons explorer comment Python détermine à quelle variable un nom fait référence, comment contrôler où les variables sont accessibles, et ce qui se passe lorsque vous supprimez un nom. À la fin, vous comprendrez les règles qui régissent la visibilité des variables dans les programmes Python.

21.1) Variables locales et globales

Chaque variable en Python existe au sein d’une portée spécifique — une région de code où ce nom de variable est défini et accessible. Les deux portées les plus fondamentales sont locale et globale.

Comprendre la portée globale

Les variables créées au niveau supérieur de votre programme — en dehors de toute fonction — existent dans la portée globale. On les appelle des variables globales, et elles sont accessibles depuis n’importe où dans votre module après avoir été définies.

python
# Variable globale - définie au niveau du module
total_users = 0
 
def show_user_count():
    # Cette fonction peut LIRE la variable globale
    print(f"Total users: {total_users}")
 
show_user_count()  # Output: Total users: 0
print(total_users)  # Output: 0

Dans cet exemple, total_users est une variable globale. À la fois la fonction show_user_count() et le code au niveau du module peuvent y accéder. Considérez les variables globales comme visibles dans l’ensemble de votre fichier de programme.

Comprendre la portée locale

Les variables créées à l’intérieur d’une fonction existent dans la portée locale de cette fonction. On les appelle des variables locales, et elles ne sont accessibles qu’au sein de la fonction où elles sont définies. Une fois l’exécution de la fonction terminée, les variables locales disparaissent.

python
def calculate_discount(price):
    # discount_rate est LOCALE à cette fonction
    discount_rate = 0.15
    discount_amount = price * discount_rate
    return discount_amount
 
result = calculate_discount(100)
print(result)  # Output: 15.0
 
# Ceci provoquerait une erreur - discount_rate n'existe pas ici
# print(discount_rate)  # NameError: name 'discount_rate' is not defined

Les variables discount_rate et discount_amount n’existent que pendant l’exécution de calculate_discount(). Après le retour de la fonction, ces noms n’existent plus. C’est en réalité une bonne chose — cela évite aux fonctions d’encombrer votre programme avec des variables temporaires.

Pourquoi la portée locale est importante

La portée locale fournit une encapsulation — chaque fonction dispose de son propre espace de travail privé. Cela signifie que vous pouvez utiliser les mêmes noms de variables dans différentes fonctions sans conflit :

python
def calculate_tax(amount):
    rate = 0.08  # Variable locale
    return amount * rate
 
def calculate_shipping(weight):
    rate = 5.00  # Variable locale différente portant le même nom
    return weight * rate
 
tax = calculate_tax(100)
shipping = calculate_shipping(3)
 
print(f"Tax: ${tax}")         # Output: Tax: $8.0
print(f"Shipping: ${shipping}")  # Output: Shipping: $15.0

Les deux fonctions utilisent une variable nommée rate, mais ce sont des variables complètement distinctes dans des portées locales différentes. Les changements de rate dans une fonction n’affectent pas rate dans l’autre fonction. Cet isolement rend les fonctions plus fiables et plus faciles à comprendre.

Lire des variables globales depuis des fonctions

Les fonctions peuvent lire des variables globales sans aucune syntaxe particulière :

python
# Configuration globale
max_login_attempts = 3
 
def check_login(password):
    # Lecture d'une variable globale
    if password == "secret123":
        return "Login successful"
    else:
        return f"Invalid password. You have {max_login_attempts} attempts."
 
result = check_login("wrong")
print(result)  # Output: Invalid password. You have 3 attempts.

La fonction check_login() peut lire max_login_attempts parce que c’est une variable globale. Cependant, il y a une limitation importante à comprendre.

La règle : l’affectation crée des variables locales

C’est ici que la portée devient délicate. Si vous affectez une valeur à un nom de variable à l’intérieur d’une fonction, Python crée une nouvelle variable locale portant ce nom, même si une variable globale portant le même nom existe :

python
counter = 0  # Variable globale
 
def increment_counter():
    # AVERTISSEMENT : ceci crée une NOUVELLE variable locale nommée counter - uniquement pour la démonstration
    # PROBLÈME : tentative de lecture de counter avant de lui affecter une valeur localement
    counter = counter + 1  # UnboundLocalError: local variable 'counter' referenced before assignment
    print(counter)
 
# increment_counter() # This call results in UnboundLocalError

Ce code échoue parce que Python voit l’affectation counter = counter + 1 et décide que counter doit être une variable locale. Mais ensuite, lorsqu’il essaie d’évaluer counter + 1, la variable locale counter n’a pas encore de valeur — nous essayons de l’utiliser avant de lui avoir affecté une valeur.

C’est une source fréquente de confusion. La règle est : si une fonction affecte une valeur à un nom de variable n’importe où dans son corps, ce nom est traité comme local dans toute la fonction, même avant l’affectation.

Voyons cela plus clairement :

python
message = "Hello"  # Variable globale
 
def show_message():
    print(message)  # Ceci fonctionne - on lit simplement la globale
    
def change_message():
    # AVERTISSEMENT : ceci illustre une erreur courante - uniquement pour la démonstration
    # PROBLÈME : Python voit l'affectation plus bas, donc message est traité comme local partout
    print(message)  # UnboundLocalError!
    message = "Goodbye"  # Ceci rend message local pour TOUTE la fonction
 
show_message()  # Output: Hello
# change_message()  # This call results in UnboundLocalError

La fonction show_message() fonctionne très bien parce qu’elle ne fait que lire message. Mais change_message() échoue parce que l’affectation sur la deuxième ligne pousse Python à traiter message comme local dans toute la fonction, y compris dans l’instruction print() qui précède l’affectation.

Les paramètres sont des variables locales

Les paramètres d’une fonction sont des variables locales qui reçoivent leurs valeurs initiales à partir des arguments passés lors de l’appel de la fonction :

python
def greet(name):  # 'name' est une variable locale
    greeting = f"Hello, {name}!"  # 'greeting' est également locale
    return greeting
 
message = greet("Alice")
print(message)  # Output: Hello, Alice!
 
# Ni 'name' ni 'greeting' n'existent ici
# print(name)  # NameError

Le paramètre name n’existe qu’à l’intérieur de la fonction greet(). Il est créé lorsque la fonction est appelée et disparaît lorsque la fonction se termine.

Exemple pratique : calcul d’un panier d’achat

Voyons comment les portées locale et globale fonctionnent ensemble dans un scénario réaliste :

python
# Configuration globale
tax_rate = 0.08
free_shipping_threshold = 50
 
def calculate_total(subtotal):
    # Variables locales pour ce calcul
    tax = subtotal * tax_rate  # Lecture de la variable globale tax_rate
    
    # Déterminer le coût d'expédition
    if subtotal >= free_shipping_threshold:  # Lecture du seuil global
        shipping = 0
    else:
        shipping = 5.99
    
    total = subtotal + tax + shipping
    return total
 
# Calculer pour différentes valeurs de panier
cart1 = calculate_total(30)
cart2 = calculate_total(60)
 
print(f"Cart 1 total: ${cart1:.2f}")  # Output: Cart 1 total: $38.39
print(f"Cart 2 total: ${cart2:.2f}")  # Output: Cart 2 total: $64.80

Dans cet exemple :

  • tax_rate et free_shipping_threshold sont des valeurs globales de configuration
  • subtotal, tax, shipping et total sont locales à chaque appel de calculate_total()
  • Chaque appel de fonction obtient son propre ensemble de variables locales distinctes
  • La fonction peut lire la configuration globale mais ne la modifie pas

Cette séparation des responsabilités rend le code clair : les variables globales contiennent une configuration qui s’applique partout, tandis que les variables locales contiennent des résultats de calcul temporaires spécifiques à chaque appel de fonction.

21.2) La règle LEGB pour la résolution des noms

Quand Python rencontre un nom de variable, comment sait-il de quelle variable vous parlez ? Python suit un ordre de recherche spécifique appelé la règle LEGB. LEGB signifie Local, Enclosing, Global, Built-in — les quatre portées que Python examine, dans cet ordre.

Les quatre portées dans LEGB

Comprenons chaque portée dans la hiérarchie LEGB :

  1. Local (L) : la portée de la fonction actuelle
  2. Enclosing (E) : la portée de toute fonction englobante (les fonctions qui contiennent la fonction actuelle)
  3. Global (G) : la portée au niveau du module
  4. Built-in (B) : les noms intégrés de Python, comme print, len, int, etc.

Quand vous utilisez un nom de variable, Python cherche dans ces portées dans l’ordre : L → E → G → B. Il utilise la première correspondance qu’il trouve et arrête la recherche.

Portée locale : le premier endroit où Python regarde

Python vérifie toujours d’abord la portée locale :

python
def calculate_price():
    price = 100  # Variable locale
    tax = 0.08   # Variable locale
    total = price * (1 + tax)
    return total
 
result = calculate_price()
print(result)  # Output: 108.0

Quand Python voit price, tax et total à l’intérieur de calculate_price(), il les trouve dans la portée locale et utilise ces valeurs. La recherche s’arrête à la portée locale — Python n’a pas besoin de chercher plus loin.

Portée globale : quand la portée locale ne l’a pas

Si un nom n’est pas trouvé localement, Python vérifie la portée globale :

python
# Variables globales
default_tax_rate = 0.08
default_currency = "USD"
 
def calculate_price(amount):
    # 'amount' est local, trouvé immédiatement
    # 'default_tax_rate' n'est pas local, trouvé dans la portée globale
    total = amount * (1 + default_tax_rate)
    return total
 
result = calculate_price(100)
print(result)  # Output: 108.0

Quand Python rencontre default_tax_rate à l’intérieur de la fonction, il ne le trouve pas localement, donc il cherche dans la portée globale et l’y trouve.

Portée intégrée : les noms prédéfinis de Python

Si un nom n’est pas trouvé dans la portée locale ou globale, Python vérifie la portée intégrée — les noms que Python fournit automatiquement :

python
def process_data(numbers):
    # 'numbers' est local
    # 'len' n'est ni local ni global - il est built-in
    count = len(numbers)
    
    # 'max' est aussi built-in
    maximum = max(numbers)
    
    return count, maximum
 
data = [10, 25, 15, 30, 20]
result = process_data(data)
print(result)  # Output: (5, 30)

Les noms len et max ne sont pas définis dans votre code — ce sont des fonctions intégrées que Python fournit. Quand Python ne trouve pas ces noms localement ou globalement, il vérifie la portée intégrée et les y trouve.

Portée englobante : fonctions imbriquées

La portée englobante entre en jeu lorsque vous avez des fonctions imbriquées — des fonctions définies à l’intérieur d’autres fonctions. C’est ici que le « E » de LEGB devient important :

python
def outer_function():
    outer_var = "I'm from outer"  # Dans la portée englobante pour inner_function
    
    def inner_function():
        inner_var = "I'm from inner"  # Local à inner_function
        # inner_function peut voir à la fois inner_var (local) et outer_var (englobant)
        print(inner_var)   # Output: I'm from inner
        print(outer_var)   # Output: I'm from outer
    
    inner_function()
 
outer_function()

Pour inner_function(), la portée de outer_function() est une portée englobante. Lorsque inner_function() référence outer_var, Python cherche :

  1. Dans la portée locale de inner_function() — non trouvé
  2. Dans la portée englobante de outer_function() — trouvé ! Utilise cette valeur

LEGB en action : exemple simple

Voyons les quatre portées travailler ensemble dans un exemple clair et direct :

python
# Built-in : len (Python fournit ceci)
# Global : multiplier
multiplier = 10
 
def outer(x):
    # Portée englobante pour inner
    y = 5
    
    def inner(z):
        # Portée locale
        # z est local (L)
        # y vient de la portée englobante (E)
        # multiplier vient de la portée globale (G)
        # len vient de la portée built-in (B)
        result = len([z, y, multiplier])  # Utilise les quatre portées !
        return z + y + multiplier
 
    return inner(3)
 
answer = outer(100)
print(answer)  # Output: 18

Quand Python évalue z + y + multiplier à l’intérieur de inner() :

  1. L (Local) : trouve z = 3
  2. E (Enclosing) : trouve y = 5 dans outer()
  3. G (Global) : trouve multiplier = 10
  4. B (Built-in) : trouve la fonction len

Cet exemple démontre clairement comment Python recherche dans les quatre portées pour résoudre les noms.

Masquage : quand les portées internes cachent les noms externes

Si le même nom existe dans plusieurs portées, la portée la plus interne « gagne » — c’est ce qu’on appelle le masquage :

python
value = "global"
 
def outer():
    value = "enclosing"
    
    def inner():
        value = "local"
        print(value)  # Which value?
    
    inner()
    print(value)  # Which value?
 
outer()
print(value)  # Which value?

Output:

local
enclosing
global

Chaque instruction print() voit une valeur différente de value parce que Python s’arrête à la première correspondance :

  • À l’intérieur de inner() : trouve value localement → affiche "local"
  • À l’intérieur de outer() mais en dehors de inner() : trouve value dans la portée de outer() → affiche "enclosing"
  • Au niveau du module : trouve value globalement → affiche "global"

Visualiser l’ordre de recherche LEGB

Oui

Non

Oui

Non

Oui

Non

Oui

Non

Référence de nom

Trouvé en Local ?

Utiliser la valeur locale

Trouvé en Englobant ?

Utiliser la valeur englobante

Trouvé en Global?

Utiliser la valeur globale

Trouvé en Built-in?

Utiliser la valeur built-in

NameError

Ce diagramme montre le processus de recherche de Python. Il commence à la portée la plus interne et progresse vers l’extérieur. Si le nom n’est trouvé dans aucune portée, Python lève une NameError.

Pourquoi LEGB compte pour écrire des fonctions

Comprendre LEGB vous aide à :

  1. Prédire les valeurs des variables : vous savez exactement quelle variable Python va utiliser
  2. Éviter les conflits de noms : vous comprenez quand des noms se masquent mutuellement
  3. Concevoir de meilleures fonctions : vous pouvez décider quelle portée est appropriée pour chaque variable
  4. Déboguer les problèmes de portée : quand les variables n’ont pas les valeurs attendues, vous pouvez remonter LEGB

La règle LEGB est fondamentale dans la façon dont Python résout les noms. Chaque fois que vous utilisez une variable, Python suit cette règle en arrière-plan.

21.3) Utiliser le mot-clé global avec précaution

Nous avons vu que les fonctions peuvent lire des variables globales, mais que faire si vous devez modifier une variable globale depuis l’intérieur d’une fonction ? C’est là que le mot-clé global intervient — mais il doit être utilisé avec parcimonie et précaution.

Le problème : l’affectation crée des variables locales

Comme nous l’avons appris plus tôt, affecter une valeur à une variable dans une fonction crée une variable locale :

python
counter = 0  # Variable globale
 
def increment():
    # AVERTISSEMENT : ceci crée une NOUVELLE variable locale nommée counter - uniquement pour la démonstration
    # PROBLÈME : tentative de lecture de counter avant de lui affecter une valeur localement
    counter = counter + 1  # UnboundLocalError!
    
# increment()  # This call results in UnboundLocalError

Cela échoue parce que Python voit l’affectation et traite counter comme local dans toute la fonction. Mais nous essayons de lire counter avant de lui avoir affecté une valeur localement.

C’est l’une des erreurs les plus courantes lorsqu’on travaille avec des variables globales. Le message d’erreur UnboundLocalError: local variable 'counter' referenced before assignment vous dit exactement ce qui s’est passé : Python a décidé que counter était local (à cause de l’affectation), mais vous avez essayé de l’utiliser avant de lui donner une valeur.

La solution : déclarer les variables comme globales

Le mot-clé global dit à Python : « Ne créez pas une nouvelle variable locale avec ce nom. Utilisez la variable globale à la place. »

python
counter = 0  # Variable globale
 
def increment():
    global counter  # Dire à Python d'utiliser le counter global
    counter = counter + 1  # Désormais, ceci modifie la variable globale
 
print(f"Before: {counter}")  # Output: Before: 0
increment()
print(f"After: {counter}")   # Output: After: 1
increment()
print(f"After again: {counter}")  # Output: After again: 2

La déclaration global counter doit venir avant d’utiliser la variable. Elle indique à Python que toute affectation à counter dans cette fonction doit modifier la variable globale, et non créer une variable locale.

Plusieurs variables globales

Vous pouvez déclarer plusieurs variables comme globales en une seule instruction :

python
total_sales = 0
total_customers = 0
 
def record_sale(amount):
    global total_sales, total_customers
    total_sales += amount
    total_customers += 1
 
print(f"Sales: ${total_sales}, Customers: {total_customers}")
# Output: Sales: $0, Customers: 0
 
record_sale(25.50)
record_sale(30.00)
 
print(f"Sales: ${total_sales}, Customers: {total_customers}")
# Output: Sales: $55.5, Customers: 2

À la fois total_sales et total_customers sont déclarées globales, donc la fonction peut modifier les deux.

Quand utiliser global : état partagé

Le mot-clé global est approprié quand vous devez maintenir un état partagé — des données auxquelles plusieurs fonctions doivent accéder et qu’elles doivent modifier :

python
# État du jeu
player_score = 0
player_lives = 3
game_over = False
 
def award_points(points):
    global player_score
    player_score += points
    print(f"Score: {player_score}")
 
def lose_life():
    global player_lives, game_over
    player_lives -= 1
    print(f"Lives remaining: {player_lives}")
    
    if player_lives <= 0:
        game_over = True
        print("Game Over!")
 
def check_game_status():
    # On lit seulement des globales - pas besoin du mot-clé global
    if game_over:
        return "Game Over"
    else:
        return f"Playing - Score: {player_score}, Lives: {player_lives}"
 
# Jouer au jeu
award_points(100)    # Output: Score: 100
award_points(50)     # Output: Score: 150
lose_life()          # Output: Lives remaining: 2
print(check_game_status())  # Output: Playing - Score: 150, Lives: 2

Cet exemple montre un usage approprié de global : plusieurs fonctions doivent modifier un état de jeu partagé. Cependant, notez que check_game_status() n’a pas besoin de global parce qu’elle ne fait que lire les variables.

Pourquoi global doit être utilisé avec précaution

Même si global est parfois nécessaire, en abuser peut rendre le code plus difficile à comprendre et à maintenir. Voici pourquoi :

Problème 1 : dépendances cachées

Quand des fonctions modifient des variables globales, ce qui change n’est pas évident depuis l’appel de la fonction :

python
total = 0
 
def add_to_total(value):
    global total
    total += value
 
# Que fait cette fonction ? Vous ne pouvez pas le savoir sans lire son code
add_to_total(10)

Comparez cela à une fonction qui renvoie une valeur :

python
def add_to_total(current_total, value):
    return current_total + value
 
total = 0
total = add_to_total(total, 10)  # Clair : total est mis à jour

La deuxième version rend explicite le fait que total est modifié.

Problème 2 : tester devient plus difficile

Les fonctions qui modifient l’état global sont plus difficiles à tester parce que vous devez initialiser et réinitialiser des variables globales :

python
# Difficile à tester - dépend d'un état global
score = 0
 
def add_score(points):
    global score
    score += points
 
# Chaque test doit réinitialiser score
# Test 1
score = 0
add_score(10)
assert score == 10
 
# Test 2 - il faut réinitialiser score à nouveau
score = 0
add_score(20)
assert score == 20

Problème 3 : les fonctions ne sont pas réutilisables

Les fonctions qui dépendent de variables globales spécifiques ne peuvent pas être facilement réutilisées dans d’autres programmes :

python
# Cette fonction ne fonctionne que s'il existe une variable globale nommée 'inventory'
inventory = []
 
def add_item(item):
    global inventory
    inventory.append(item)

Meilleures alternatives à global

Dans de nombreux cas, vous pouvez éviter global en utilisant des valeurs de retour et des paramètres :

Au lieu de modifier l’état global :

python
# Utiliser global (moins idéal)
balance = 1000
 
def withdraw(amount):
    global balance
    if amount <= balance:
        balance -= amount
        return True
    return False
 
withdraw(100)
print(balance)  # Output: 900

Utiliser des valeurs de retour :

python
# Utiliser des valeurs de retour (mieux)
def withdraw(balance, amount):
    if amount <= balance:
        return balance - amount, True
    return balance, False
 
balance = 1000
balance, success = withdraw(balance, 100)
print(balance)  # Output: 900

La deuxième version est plus flexible, plus testable et plus réutilisable.

Quand global est réellement approprié

Il existe des usages légitimes de global :

  1. Une configuration qui a réellement besoin d’être globale :
python
# Paramètres à l'échelle de l'application
debug_mode = False
log_level = "INFO"
 
def enable_debug():
    global debug_mode, log_level
    debug_mode = True
    log_level = "DEBUG"
  1. Des compteurs pour le débogage ou des statistiques :
python
# Suivre les appels de fonction pour le débogage
_function_call_count = 0
 
def tracked_function():
    global _function_call_count
    _function_call_count += 1
    # ... reste de la fonction

Points clés à propos de global

  • Utilisez global uniquement lorsque vous devez réellement modifier un état au niveau du module
  • Préférez plutôt renvoyer des valeurs et utiliser des paramètres
  • Lorsque vous utilisez global, documentez pourquoi c’est nécessaire
  • Demandez-vous si votre conception pourrait être améliorée pour éviter global
  • Rappelez-vous : lire des variables globales ne nécessite pas le mot-clé global — seule leur modification le nécessite

21.4) Utiliser nonlocal pour modifier des variables dans des fonctions englobantes

Lorsque vous avez des fonctions imbriquées, vous pourriez avoir besoin de modifier une variable issue de la portée d’une fonction englobante. Le mot-clé nonlocal sert à cela — il est comme global, mais pour les portées des fonctions englobantes plutôt que pour la portée globale.

Le problème : modifier des variables englobantes

Tout comme l’affectation crée des variables locales par défaut, le même problème se produit avec les portées englobantes :

python
def outer():
    count = 0  # Variable dans la portée de outer
    
    def inner():
        # AVERTISSEMENT : ceci crée une NOUVELLE variable locale nommée count - uniquement pour la démonstration
        # PROBLÈME : tentative de lecture de count avant de lui affecter une valeur localement
        count = count + 1  # UnboundLocalError!
        print(count)
    
    inner()
 
# outer()  # This call results in UnboundLocalError

Python voit l’affectation à count dans inner() et la traite comme une variable locale. Mais nous essayons de la lire avant de lui affecter une valeur localement, ce qui provoque une erreur.

La solution : le mot-clé nonlocal

Le mot-clé nonlocal dit à Python : « Cette variable n’est pas locale — cherchez-la dans la portée de la fonction englobante et utilisez celle-là. »

python
def outer():
    count = 0  # Variable dans la portée de outer
    
    def inner():
        nonlocal count  # Utiliser le count de la portée de outer
        count = count + 1
        print(f"Count in inner: {count}")
    
    print(f"Count before: {count}")  # Output: Count before: 0
    inner()                          # Output: Count in inner: 1
    print(f"Count after: {count}")   # Output: Count after: 1
 
outer()

Désormais, inner() peut modifier la variable count de la portée de outer(). Le changement persiste après le retour de inner() parce que nous modifions la variable réelle dans la portée englobante.

Pourquoi nonlocal est utile : des fonctions qui se souviennent d’un état

Le mot-clé nonlocal permet un pattern puissant où les fonctions internes peuvent maintenir et modifier un état issu de leur portée englobante. Nous apprendrons en détail les closures et les fonctions fabrique au Chapitre 23, mais pour l’instant, comprenez que nonlocal permet aux fonctions internes de modifier des variables provenant de portées englobantes.

Voici un exemple simple montrant comment nonlocal fonctionne :

python
def create_counter():
    count = 0  # Cette variable est dans la portée englobante pour increment
    
    def increment():
        nonlocal count  # Modifier count depuis la portée englobante
        count += 1
        return count
    
    return increment  # Renvoyer la fonction interne
 
# Créer un compteur
counter1 = create_counter()
 
print(counter1())  # Output: 1
print(counter1())  # Output: 2
print(counter1())  # Output: 3
 
# Créer un autre compteur indépendant
counter2 = create_counter()
 
print(counter2())  # Output: 1
print(counter2())  # Output: 2

Chaque appel à create_counter() crée une nouvelle variable count et une nouvelle fonction increment() qui peut modifier ce count spécifique en utilisant nonlocal.

nonlocal vs global

Il est important de comprendre la différence :

python
x = "global"
 
def outer():
    x = "enclosing"
    
    def use_global():
        global x  # Fait référence au x global
        print(f"use_global sees: {x}")  # Output: use_global sees: global
    
    def use_nonlocal():
        nonlocal x  # Fait référence au x de outer
        print(f"use_nonlocal sees: {x}")  # Output: use_nonlocal sees: enclosing
    
    use_global()
    use_nonlocal()
 
outer()
  • global fait toujours référence à la portée au niveau du module
  • nonlocal fait référence à la portée de la fonction englobante la plus proche

Quand vous ne pouvez pas utiliser nonlocal

Le mot-clé nonlocal ne fonctionne qu’avec les portées des fonctions englobantes. Vous ne pouvez pas l’utiliser pour :

  1. La portée globale (utilisez global à la place) :
python
x = "global"
 
def func():
    nonlocal x  # SyntaxError: no binding for nonlocal 'x' found
    x = "modified"
  1. Des variables qui n’existent dans aucune portée englobante :
python
def outer():
    def inner():
        nonlocal count  # SyntaxError: no binding for nonlocal 'count' found

Points clés à propos de nonlocal

  • Utilisez nonlocal pour modifier des variables provenant des portées de fonctions englobantes
  • nonlocal cherche dans les portées des fonctions englobantes, pas dans la portée globale
  • Lire des variables englobantes ne nécessite pas nonlocal — seule leur modification le nécessite
  • nonlocal permet des patterns puissants pour créer des fonctions avec un état privé
  • Nous en apprendrons davantage sur les closures et les fonctions fabrique au Chapitre 23

Le mot-clé nonlocal est particulièrement utile pour créer des fonctions qui maintiennent un état privé, comme nous l’avons vu avec l’exemple de compteur.

21.5) Supprimer des noms (pas des objets) avec del et ce que cela signifie

Parfois, vous devez retirer une variable de l’espace de noms de votre programme — par exemple pour libérer de la mémoire dans des programmes de longue durée, nettoyer des variables temporaires, ou supprimer des entrées de collections. L’instruction del de Python gère ces tâches, mais il est important de comprendre exactement ce qu’elle fait et ne fait pas.

L’instruction del en Python est souvent mal comprise. Elle ne supprime pas des objets — elle supprime des noms (liaisons de variables). Comprendre cette distinction est crucial pour comprendre comment Python gère la mémoire et les références.

Ce que del fait réellement

L’instruction del supprime un nom de la portée actuelle :

python
x = 42
print(x)  # Output: 42
 
del x
 
# print(x)  # NameError: name 'x' is not defined

Après del x, le nom x n’existe plus dans la portée actuelle. Si vous essayez de l’utiliser, Python lève une NameError parce que le nom n’est plus défini.

Supprimer des noms vs supprimer des objets

Voici l’idée clé : del supprime le nom, pas nécessairement l’objet auquel le nom fait référence :

python
# Créer une liste et deux noms qui y font référence
original = [1, 2, 3]
reference = original  # Les deux noms font référence à la même liste
 
print(original)   # Output: [1, 2, 3]
print(reference)  # Output: [1, 2, 3]
 
# Supprimer un des noms
del original
 
# La liste existe toujours car 'reference' y fait encore référence
print(reference)  # Output: [1, 2, 3]
 
# print(original)  # NameError: name 'original' is not defined

La liste [1, 2, 3] continue d’exister parce que reference y fait encore référence. Supprimer original n’a fait que retirer ce nom — cela n’a pas supprimé l’objet liste lui-même.

Quand les objets sont réellement supprimés

Python supprime automatiquement les objets lorsqu’ils ne sont plus référencés par aucun nom. Cela s’appelle le ramasse-miettes (garbage collection) :

python
data = [1, 2, 3]  # La liste est créée, 'data' y fait référence
 
del data  # Le nom 'data' est supprimé
 
# Maintenant la liste n'a plus de références, donc Python finira par la supprimer
# (Cela se fait automatiquement - vous n'avez rien à faire)

Lorsque nous supprimons data, la liste [1, 2, 3] n’a plus de références restantes, donc le ramasse-miettes de Python finira par récupérer la mémoire. Mais cela se fait automatiquement — vous ne contrôlez pas quand cela arrive.

Supprimer des éléments de collections

L’instruction del peut aussi retirer des éléments de collections, mais c’est fondamentalement différent de supprimer des noms. Lorsque vous utilisez del avec un index ou une tranche, vous modifiez la collection elle-même, et non la suppression d’un nom.

C’est une distinction importante : quand vous écrivez del numbers[2], vous appelez une méthode spéciale sur l’objet liste pour retirer un élément. Le nom numbers existe toujours et fait toujours référence au même objet liste — la liste a simplement moins d’éléments maintenant.

python
# Supprimer des éléments de liste par index
numbers = [10, 20, 30, 40, 50]
del numbers[2]  # Retirer l'élément à l'index 2
print(numbers)  # Output: [10, 20, 40, 50]
 
# Supprimer des tranches de liste
numbers = [10, 20, 30, 40, 50]
del numbers[1:3]  # Retirer les éléments de l'index 1 à 3 (exclus)
print(numbers)  # Output: [10, 40, 50]
 
# Supprimer des entrées de dictionnaire
person = {'name': 'Alice', 'age': 30, 'city': 'Boston'}
del person['age']
print(person)  # Output: {'name': 'Alice', 'city': 'Boston'}
© 2025. Primesoft Co., Ltd.
support@primesoft.ai