# La portée (Scope) : Local, Enclosing, Global et Built-in (LEGB)<no value>

## Qu'allons-nous apprendre ?

**La portée** (Scope) = **la zone du code où un nom (une variable ou une fonction) est connu**. Nous allons apprendre :

- **Local** — les variables dans une fonction.
- **Enclosing** — les variables d'une fonction extérieure, quand il y a une fonction dans une fonction.
- **Global** — les variables au niveau supérieur du fichier.
- **Built-in** — les noms intégrés de Python (`print`, `len`...).
- **LEGB** — l'ordre dans lequel Python cherche un nom.

## Pourquoi en avons-nous besoin ?

Si chaque variable était accessible de partout, une variable nommée `total` dans une fonction écraserait une variable `total` dans une autre fonction, et dans un programme de 10 000 lignes ce serait le chaos. La portée **isole** chaque fonction : elle peut utiliser ses propres noms sans craindre de collisions.

D'un autre côté, des erreurs comme `NameError` ou `UnboundLocalError` sont très déroutantes si l'on ne comprend pas la portée.

## Explication à partir des bases

### Local

Une variable créée **dans une fonction** (y compris ses paramètres) est **locale** :

- Elle est créée **à chaque appel** de la fonction, et disparaît **quand elle se termine**.
- Elle n'est **pas accessible de l'extérieur**.
- Deux fonctions différentes peuvent avoir des variables locales du même nom, sans aucun lien entre elles.

### Global

Une variable créée **au niveau supérieur du fichier** (pas dans une fonction) est **globale** :

- Elle est accessible **en lecture** depuis toutes les fonctions du fichier.
- Pour la **modifier** (lui affecter une nouvelle valeur) depuis une fonction, il faut une déclaration explicite : `global name`.

### Enclosing

On peut définir **une fonction dans une fonction**. La fonction intérieure voit les variables de la fonction extérieure. La portée de la fonction extérieure s'appelle **Enclosing** (englobante) du point de vue de la fonction intérieure.

### Built-in

Les noms que Python connaît toujours, sans `import` : `print`, `len`, `sum`, `max`, `int`, `str`, `list`, `range`...

### L'ordre de recherche : LEGB

Quand le code utilise un nom, Python le cherche **dans cet ordre**, et s'arrête **au premier endroit** où il l'a trouvé :

```text
L  Local       → inside the current function
E  Enclosing   → inside outer functions (if nested)
G  Global      → top level of the file
B  Built-in    → Python's built-in names
              → not found anywhere: NameError
```

{{< box "analogy" "Analogie : où sont les clés ?" >}}
Vous cherchez vos clés : d'abord **dans votre poche** (Local), puis **dans le sac** que vous portez (Enclosing), puis **à la maison** (Global), et enfin **dans le hall de l'immeuble**, où l'on laisse des choses pour tout le monde (Built-in). Dès que vous les trouvez, vous arrêtez de chercher. Et une clé dans la poche d'un ami, vous ne pouvez pas la voir du tout : c'est le Local d'une autre fonction.
{{< /box >}}

## Syntaxe

```python
counter = 0                  # global

def outer():
    message = "hi"           # local to outer = enclosing for inner

    def inner():
        result = message + "!"   # local to inner; message found in Enclosing
        return result

    return inner()

def increase():
    global counter           # declare: assignments go to the GLOBAL counter
    counter = counter + 1
```

## Premier exemple de code

`week-2/scope_demo.py` :

```python
course = "Kodkod 6"

def welcome(name):
    message = "Welcome " + name + " to " + course
    return message

print(welcome("Dana"))
print(course)
print(message)
```

**Sortie :**

```text
Welcome Dana to Kodkod 6
Kodkod 6
Traceback (most recent call last):
  File "scope_demo.py", line 11, in <module>
    print(message)
NameError: name 'message' is not defined
```

## Analyse du code ligne par ligne

| Nom | Portée | Explication |
|---|---|---|
| `course` | **Global** | Créé au niveau supérieur. `welcome` le **lit** : L (pas trouvé) → E (aucun) → G (trouvé !). |
| `name` | **Local** de `welcome` | Un paramètre = une variable locale. N'existe que pendant l'appel. |
| `message` | **Local** de `welcome` | Créé dans la fonction et disparaît quand elle renvoie. |
| `print`, `len`... | **Built-in** | Trouvés à la fin de la recherche. |
| `print(message)` à l'extérieur | — | Au niveau supérieur, il n'y a pas de `message`. C'était un Local, et il a déjà disparu, d'où la `NameError`. |

**La bonne façon de sortir une valeur d'une fonction est `return`**, et c'est exactement ce que fait `welcome`. L'appelant a reçu la valeur (et peut la stocker dans sa propre variable).

## Un autre exemple

**Le même nom dans plusieurs portées, et `global`.** `week-2/scope_levels.py` :

```python
total = 100

def local_total():
    total = 5
    print("inside local_total:", total)

def read_global():
    print("inside read_global:", total)

def change_global():
    global total
    total = total + 50
    print("inside change_global:", total)

def outer():
    label = "outer"

    def inner():
        print("inner sees enclosing label:", label)

    inner()

local_total()
print("after local_total:", total)
read_global()
change_global()
print("after change_global:", total)
outer()
```

**Sortie :**

```text
inside local_total: 5
after local_total: 100
inside read_global: 100
inside change_global: 150
after change_global: 150
inner sees enclosing label: outer
```

**Analyse :**

- `local_total` — l'affectation `total = 5` crée **une nouvelle variable locale** nommée `total`, qui « cache » la globale. La globale **n'a pas changé** (100).
- `read_global` — ne fait que **lire** `total`. Elle n'a pas de variable locale de ce nom, donc selon LEGB on arrive à la globale.
- `change_global` — `global total` dit à Python : « dans cette fonction, `total` est la globale ». Maintenant l'affectation modifie la globale (150).
- `inner` voit le `label` de `outer`. C'est le **E** de LEGB.

## Erreur courante

**Erreur 1 : `UnboundLocalError` — lire puis modifier une globale sans `global`.**

```python
count = 0

def add_one():
    count = count + 1

add_one()
```

```text
UnboundLocalError: cannot access local variable 'count' where it is not associated with a value
```

**Pourquoi ?** Parce que la fonction contient une **affectation** à `count`, Python décide (à l'avance, avant l'exécution) que `count` est **local** dans toute la fonction. Et alors `count + 1` essaie de lire une variable locale qui n'a pas encore reçu de valeur.

**Erreur 2 (la grande) : utiliser `global` comme solution permanente.** `global` « fonctionne », mais :

- Chaque fonction peut modifier la variable, et quand il y a un bug, impossible de savoir qui l'a modifiée.
- La fonction dépend de quelque chose « venu de l'extérieur », il est donc difficile de la tester et de la réutiliser.

**La façon propre : un paramètre entre, `return` sort.**

```python
def add_one(count):
    return count + 1

count = 0
count = add_one(count)
print(count)   # 1
```

Maintenant, ce qui entre et ce qui sort est tout à fait clair, et la fonction ne touche à rien qu'elle n'a pas reçu. (Dans le thème POO, nous apprendrons une autre façon de gérer un « état » qui change : les objets.)

**Erreur 3 : écraser un nom Built-in.**

```python
list = [1, 2, 3]
numbers = list(range(5))
```

```text
TypeError: 'list' object is not callable
```

Notre `list` globale vient **avant** le Built-in dans LEGB, elle a donc « caché » la fonction intégrée. C'est la même chose avec `sum = 0`, `max = 10`, `input = ...`, `str = ...`. **Ne donnez jamais aux variables les noms des Built-ins.**

<!-- exercises:start — generated by scripts/intro6/sync_site_exercises.py; edit scripts/intro6/exercises/ -->
## Exercices

Les exercices n'utilisent que ce qui a été vu dans cette leçon et les précédentes. Essayez d'abord seul, et n'ouvrez la solution qu'ensuite.

### Exercice 1: La variable qui a disparu

```python
def welcome(name):
    message = "Welcome " + name
    return message

print(welcome("Dana"))
print(message)
```

1. Que se passera-t-il à la dernière ligne, et pourquoi ?
2. Corrigez pour que le message soit affiché deux fois.

{{< solution >}}
1. `NameError: name 'message' is not defined`. `message` est une variable **Local** de `welcome` — elle est créée à chaque appel et disparaît quand la fonction se termine.
2. Enregistrez la valeur renvoyée dans notre propre variable :

```python
def welcome(name):
    message = "Welcome " + name
    return message


result = welcome("Dana")
print(result)
print(result)
```

```text
Welcome Dana
Welcome Dana
```
{{< /solution >}}

### Exercice 2: Prédire la sortie

```python
x = "global"

def first():
    x = "local in first"
    print("1:", x)

def second():
    print("2:", x)

first()
second()
print("3:", x)
```

{{< solution >}}
```python
x = "global"


def first():
    x = "local in first"
    print("1:", x)


def second():
    print("2:", x)


first()
second()
print("3:", x)
```

```text
1: local in first
2: global
3: global
```

- `first` crée son propre `x` **local** — la globale n'a pas été touchée.
- `second` ne définit pas `x`, donc selon **LEGB** on arrive à la Global.
{{< /solution >}}

### Exercice 3: UnboundLocalError

```python
count = 0

def add_one():
    count = count + 1

add_one()
```

1. Quelle erreur obtiendrez-vous, et pourquoi ?
2. Corrigez sans `global`.

{{< solution >}}
1. **`UnboundLocalError`**. À cause de l'affectation `count = ...`, Python décide que `count` est une variable **locale** dans toute la fonction — et alors `count + 1` essaie de lire une variable locale qui n'a pas encore de valeur.
2. Un paramètre entre, `return` sort :

```python
def add_one(count):
    return count + 1


count = 0
count = add_one(count)
count = add_one(count)
print(count)
```

```text
2
```
{{< /solution >}}

### Exercice 4: Écraser un nom intégré

```python
list = [1, 2, 3]
numbers = list(range(5))
```

1. Quelle erreur obtiendrez-vous ?
2. Expliquez avec LEGB pourquoi cela arrive, et corrigez.

{{< solution >}}
1. `TypeError: 'list' object is not callable`.
2. Selon **LEGB**, Python cherche `list` dans **Global** avant **Built-in**. Notre variable nommée `list` a « caché » la fonction intégrée. La correction — ne pas donner aux variables les noms des Built-ins :

```python
items = [1, 2, 3]
numbers = list(range(5))
print(items, numbers)
```

```text
[1, 2, 3] [0, 1, 2, 3, 4]
```
{{< /solution >}}

### Exercice 5: Sans global

```python
cart = []
total = 0

def add(price):
    global total
    cart.append(price)
    total = total + price

add(100)
add(50)
print(cart, total)
```

Refactorisez pour qu'aucune fonction n'utilise `global` ni ne modifie de variables extérieures : chaque fonction reçoit ce dont elle a besoin par des paramètres et renvoie un résultat.

**Résultat attendu :**

```text
[100, 50] 150
```

{{< solution >}}
```python
def add(cart, price):
    return cart + [price]


def cart_total(cart):
    return sum(cart)


cart = []
cart = add(cart, 100)
cart = add(cart, 50)
print(cart, cart_total(cart))
```

```text
[100, 50] 150
```

- `total` n'est plus un « état » à maintenir — il est **calculé** à partir du panier.
- `add` renvoie une **nouvelle** liste (`cart + [price]`) et ne modifie pas l'existante.
{{< /solution >}}

<!-- exercises:end -->

## Résumé

- **La portée** = là où un nom est connu. Une variable créée dans une fonction (y compris les paramètres) est **Local**, et disparaît à la fin de l'appel.
- **LEGB** : Local → Enclosing → Global → Built-in. On s'arrête au premier endroit où il est trouvé. S'il n'est pas trouvé, `NameError`.
- Une affectation dans une fonction crée une variable **locale**, même s'il existe une globale du même nom.
- `global x` permet de modifier une globale depuis une fonction, mais **il vaut mieux l'éviter**. Un paramètre entre et `return` sort.
- Ne donnez pas aux variables les noms des Built-ins (`list`, `sum`, `max`, `str`...).

<!-- docs:start — generated by scripts/intro6/site_docs.py; edit the links there -->

## Documentation officielle

- [Python Scopes and Namespaces](https://docs.python.org/3/tutorial/classes.html#python-scopes-and-namespaces)
- [Resolution of names](https://docs.python.org/3/reference/executionmodel.html#resolution-of-names)
- [The global statement](https://docs.python.org/3/reference/simple_stmts.html#the-global-statement)

<!-- docs:end -->
