# Les packages, __init__.py et if __name__ == "__main__"<no value>

## Qu'allons-nous apprendre ?

- **Le package** — un dossier qui contient des modules.
- Le fichier **`__init__.py`** et son rôle.
- Importer depuis un package.
- La variable spéciale **`__name__`**, et le modèle **`if __name__ == "__main__":`**.

## Pourquoi en avons-nous besoin ?

Quand il y a 3 modules, ils peuvent être dans le même dossier. Quand il y en a 30, il faut des **dossiers** : tout ce qui concerne les utilisateurs dans un dossier, tout ce qui concerne les paiements dans un autre. C'est ainsi que sont construits tous les vrais projets, et aussi toutes les bibliothèques que vous installerez (FastAPI est un énorme package avec des dizaines de sous-packages).

Et `if __name__ == "__main__":` résout le problème de la leçon précédente : du code de test qui s'exécute chaque fois que quelqu'un importe le fichier.

## Explication à partir des bases

### Package = un dossier de modules

Un **package** est un dossier qui contient des modules (des fichiers `.py`), et généralement aussi un fichier nommé **`__init__.py`**.

```text
store/                    ← project folder
├── main.py               ← the program you run
└── inventory/            ← a PACKAGE
    ├── __init__.py       ← marks this folder as a package
    ├── products.py       ← module
    └── stock.py          ← module
```

### `__init__.py`

- **Marque** le dossier comme un package, de façon explicite et claire.
- **S'exécute automatiquement** la première fois que quelque chose est importé du package.
- Généralement **vide**. On peut y mettre des imports pratiques, pour pouvoir écrire `from inventory import add_product` au lieu de `from inventory.products import add_product`.

(En Python moderne, on peut aussi importer depuis un dossier sans `__init__.py`, mais la convention en usage est de l'inclure, pour qu'il soit clair que c'est un package et pas un simple dossier.)

### Importer depuis un package

Le point sépare un **dossier** d'un module, exactement comme `/` dans un chemin :

```python
import inventory.products                 # inventory/products.py
from inventory import products            # same module, shorter use
from inventory.products import add_product   # one function
```

### `__name__` — « qui suis-je ? »

Chaque module a une variable intégrée nommée **`__name__`**. Python fixe sa valeur selon **la façon dont le fichier a été lancé** :

| Comment le fichier a été lancé | La valeur de `__name__` |
|---|---|
| **Exécuté directement** : `python prices.py` | `"__main__"` |
| **Importé** depuis un autre fichier : `import prices` | `"prices"` (le nom du module) |

Et donc :

```python
if __name__ == "__main__":
    # runs ONLY when this file is run directly, NOT when it is imported
```

{{< box "analogy" "Analogie : un chef qui est aussi fournisseur" >}}
Un chef cuisine dans son propre restaurant (exécution directe), et en même temps vend des sauces aux magasins (import). Quand il est au restaurant, il sert aussi les clients : c'est le code dans `if __name__ == "__main__"`. Quand un magasin achète une sauce, il reçoit **seulement la sauce** (les fonctions), et pas un repas entier qu'on lui imposerait.
{{< /box >}}

## Syntaxe

```text
project/
├── main.py
└── package_name/
    ├── __init__.py
    └── module_name.py
```

```python
# main.py
from package_name.module_name import some_function
from package_name import module_name

# any module that can also be run directly:
def main():
    ...          # what happens when the file is run as a program

if __name__ == "__main__":
    main()
```

Le modèle d'une fonction `main()` + la condition à la fin du fichier est **la norme** dans tout fichier Python destiné à être exécuté.

## Premier exemple de code

Voyons `__name__` en action. `week-2/name_demo/greetings.py` :

```python
def hello(name):
    return "Hello, " + name + "!"

print("greetings.py: __name__ is", __name__)

if __name__ == "__main__":
    print("Running greetings.py directly - quick self test:")
    print(hello("Tester"))
```

`week-2/name_demo/app.py` :

```python
import greetings

print("app.py: __name__ is", __name__)
print(greetings.hello("Dana"))
```

**Exécution 1 :** `python greetings.py`

```text
greetings.py: __name__ is __main__
Running greetings.py directly - quick self test:
Hello, Tester!
```

**Exécution 2 :** `python app.py`

```text
greetings.py: __name__ is greetings
app.py: __name__ is __main__
Hello, Dana!
```

## Analyse du code ligne par ligne

| | Exécution 1 (`python greetings.py`) | Exécution 2 (`python app.py`) |
|---|---|---|
| `__name__` de `greetings.py` | `"__main__"` (c'est le fichier exécuté) | `"greetings"` (il a été importé) |
| Le premier `print` de greetings | S'exécute | **S'exécute** (le code au niveau supérieur s'exécute toujours à l'import) |
| Le bloc `if __name__ == "__main__"` | **S'exécute** (auto-test) | **Ne s'exécute pas** |
| `__name__` de `app.py` | — | `"__main__"` |

**La conclusion :** tout ce qui doit s'exécuter **uniquement quand le fichier est lancé directement** (tests, démonstrations, démarrage du programme) va dans la condition. Les définitions de fonctions et de constantes restent à l'extérieur, pour pouvoir être importées.

## Un autre exemple

**Un projet avec un vrai package.** La structure :

```text
week-2/store/
├── main.py
└── inventory/
    ├── __init__.py
    ├── products.py
    └── stock.py
```

`inventory/__init__.py` :

```python
from inventory.products import create_product
from inventory.stock import add_stock, remove_stock
```

`inventory/products.py` :

```python
def create_product(name, price):
    if price < 0:
        raise ValueError("price cannot be negative, got " + str(price))
    return {"name": name, "price": price, "quantity": 0}

if __name__ == "__main__":
    print(create_product("Test item", 10))
```

`inventory/stock.py` :

```python
def add_stock(product, amount):
    product["quantity"] = product["quantity"] + amount

def remove_stock(product, amount):
    if amount > product["quantity"]:
        raise ValueError("not enough " + product["name"] + " in stock")
    product["quantity"] = product["quantity"] - amount
```

`main.py` :

```python
from inventory import create_product, add_stock, remove_stock

def main():
    laptop = create_product("Laptop", 3500)
    add_stock(laptop, 10)
    remove_stock(laptop, 3)
    print(laptop)

    try:
        remove_stock(laptop, 50)
    except ValueError as error:
        print("Error:", error)

if __name__ == "__main__":
    main()
```

**Exécution** (depuis `week-2/store`) : `python main.py`

```text
{'name': 'Laptop', 'price': 3500, 'quantity': 7}
Error: not enough Laptop in stock
```

**Analyse :**

- `__init__.py` **rassemble** les fonctions importantes du package. Grâce à lui, `main.py` écrit `from inventory import create_product`, et n'a pas besoin de savoir dans quel fichier interne elle se trouve exactement. De l'extérieur, le package ressemble à « une seule unité ».
- `add_stock(laptop, 10)` **modifie le dict lui-même**, car un dict est Mutable (le thème structures de données et fonctions !). La fonction n'a rien besoin de renvoyer.
- `products.py` contient un auto-test dans `if __name__ == "__main__"`. On peut l'exécuter seul pour le tester, et il ne « fait pas de désordre » quand il est importé.
- `main.py` est enveloppé dans `main()` + la condition. Si quelqu'un l'importe à l'avenir, rien ne s'exécutera tout seul.

{{< box "tip" "Comment exécuter un fichier qui se trouve dans un package ?" >}}
Si vous essayez `python inventory/products.py`, vous obtenez parfois une `ModuleNotFoundError`, car Python cherche les modules depuis le dossier **du fichier exécuté** (`inventory/`), et non depuis le dossier du projet. La bonne façon d'exécuter un module dans un package est depuis le dossier du projet, avec l'option `-m` (module) et **des points à la place des barres obliques** : `python -m inventory.products`.
{{< /box >}}

## Erreur courante

**Erreur 1 : écrire `"main"` au lieu de `"__main__"`.**

```python
if __name__ == "main":
    main()
```

Rien ne s'exécute, et il n'y a aucune erreur ! La valeur est `"__main__"`, avec **deux tirets bas de chaque côté**. Une Logical Error silencieuse.

**Erreur 2 : un import circulaire (Circular Import).**

`a.py` importe `b`, et `b.py` importe `a` :

```text
ImportError: cannot import name 'x' from partially initialized module 'a' (most likely due to a circular import)
```

Deux modules qui dépendent l'un de l'autre sont le signe d'un mauvais découpage. Déplacez la partie commune dans un troisième module, dont les deux importent.

**Erreur 3 : mettre toute la logique dans le bloc `if`.**

```python
if __name__ == "__main__":
    def calculate(x):
        return x * 2
    print(calculate(5))
```

Maintenant, `calculate` ne peut pas être importée depuis un autre fichier, car elle n'est définie que lorsque le fichier est lancé directement. Les fonctions vont **à l'extérieur**, et seul **le démarrage du programme** va à l'intérieur.

<!-- 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 structure d'un package

Dessinez la structure de dossiers d'un projet `store` avec un fichier `main.py` et un package nommé `inventory`, qui contient deux modules : `products.py` et `stock.py`. Quel fichier doit se trouver dans `inventory`, et pourquoi ?

{{< solution >}}
```text
store/
├── main.py
└── inventory/
    ├── __init__.py
    ├── products.py
    └── stock.py
```

`__init__.py` marque le dossier comme un **package** — un dossier de modules depuis lequel on peut importer. Il peut être vide.
{{< /solution >}}

### Exercice 2: Importer depuis un package

Dans `inventory/products.py`, il y a une fonction `create_product(name, price)` qui renvoie un dictionnaire. Écrivez dans `main.py` trois façons de l'importer et de l'utiliser.

{{< solution >}}
```py
import inventory.products
laptop = inventory.products.create_product("Laptop", 3500)

from inventory import products
mouse = products.create_product("Mouse", 80)

from inventory.products import create_product
desk = create_product("Desk", 900)
```

Le point dans le chemin (`inventory.products`) est comme `/` dans les dossiers : le module `products` dans le package `inventory`.
{{< /solution >}}

### Exercice 3: Qu'est-ce que __name__ ?

Dans le fichier `greetings.py` :

```text
def hello(name):
    return "Hello, " + name + "!"

print("__name__ is", __name__)
```

Qu'est-ce qui sera affiché dans chaque cas ?

1. Vous lancez `python greetings.py`.
2. Vous lancez `main.py`, qui contient `import greetings`.

{{< solution >}}
1. En exécution directe : `__name__ is __main__`.
2. À l'import : `__name__ is greetings` — le nom du module.

Python fixe `__name__` selon **la façon** dont le fichier a été lancé. C'est la base du main guard.
{{< /solution >}}

### Exercice 4: Le main guard

Ajoutez à `greetings.py` (de l'exercice précédent, avec la fonction `hello`) un auto-test qui ne s'exécute **que** lorsque le fichier est lancé directement, et pas quand il est importé. Le test affiche `quick self test:` puis le résultat de `hello("Tester")`.

Supprimez la ligne `print("__name__ is", __name__)` de l'exercice précédent.

**Résultat attendu (python greetings.py) :**

```text
quick self test:
Hello, Tester!
```

{{< solution >}}
```python
def hello(name):
    return "Hello, " + name + "!"


if __name__ == "__main__":
    print("quick self test:")
    print(hello("Tester"))
```

```text
quick self test:
Hello, Tester!
```

Quand un autre fichier fait `import greetings`, la condition vaut `False` — le test ne s'exécute pas, et seule la fonction est disponible.
{{< /solution >}}

### Exercice 5: Le package mathtools

Construisez un package nommé `mathtools` avec deux modules : `basic.py` avec `add(a, b)`, et `stats.py` avec `mean(numbers)` et `maximum(numbers)`. Dans `main.py`, utilisez les trois.

```text
add(2, 3)
mean([10, 20, 30])
maximum([10, 20, 30])
```

**Résultat attendu :**

```text
add: 5
mean: 20.0
maximum: 30
```

{{< solution >}}
```text
math_project/
├── main.py
└── mathtools/
    ├── __init__.py
    ├── basic.py
    └── stats.py
```

**mathtools/basic.py**

```py
def add(a, b):
    return a + b
```

**mathtools/stats.py**

```py
def mean(numbers):
    return sum(numbers) / len(numbers)


def maximum(numbers):
    return max(numbers)
```

**main.py**

```py
from mathtools.basic import add
from mathtools import stats

print("add:", add(2, 3))
print("mean:", stats.mean([10, 20, 30]))
print("maximum:", stats.maximum([10, 20, 30]))
```
{{< /solution >}}

<!-- exercises:end -->

## Résumé

- **Package** = un dossier de modules, avec un **`__init__.py`** qui s'exécute au premier import et peut « exposer » des noms pratiques.
- Importer depuis un package : `from package.module import name`. Le point = entrer dans un dossier.
- **`__name__`** vaut `"__main__"` quand le fichier a été lancé directement, et le nom du module quand il a été importé.
- **`if __name__ == "__main__": main()`** — démarrer le programme uniquement en exécution directe. Définissez les fonctions à l'extérieur.
- Un module dans un package s'exécute depuis le dossier du projet avec `python -m package.module`.

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

## Documentation officielle

- [Python Tutorial — Packages](https://docs.python.org/3/tutorial/modules.html#packages)
- [__main__ — Top-level code environment](https://docs.python.org/3/library/__main__.html#name-main)

<!-- docs:end -->
