Les packages, __init__.py et if __name__ == "__main__"
- Expliquer ce qu’est un package et en construire un avec
__init__.py - Importer des modules et des fonctions depuis un package
- Expliquer quelle est la valeur de
__name__quand un fichier est exécuté et quand il est importé - Utiliser
if __name__ == "__main__":pour séparer une « bibliothèque » d’un « programme »
Qu’allons-nous apprendre ?
- Le package — un dossier qui contient des modules.
- Le fichier
__init__.pyet son rôle. - Importer depuis un package.
- La variable spéciale
__name__, et le modèleif __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.
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_productau lieu defrom 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 :
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 :
if __name__ == "__main__":
# runs ONLY when this file is run directly, NOT when it is importedUn 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.
Syntaxe
project/
├── main.py
└── package_name/
├── __init__.py
└── module_name.py# 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 :
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 :
import greetings
print("app.py: __name__ is", __name__)
print(greetings.hello("Dana"))Exécution 1 : python greetings.py
greetings.py: __name__ is __main__
Running greetings.py directly - quick self test:
Hello, Tester!Exécution 2 : python app.py
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 :
week-2/store/
├── main.py
└── inventory/
├── __init__.py
├── products.py
└── stock.pyinventory/__init__.py :
from inventory.products import create_product
from inventory.stock import add_stock, remove_stockinventory/products.py :
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 :
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"] - amountmain.py :
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
{'name': 'Laptop', 'price': 3500, 'quantity': 7}
Error: not enough Laptop in stockAnalyse :
__init__.pyrassemble les fonctions importantes du package. Grâce à lui,main.pyécritfrom 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.pycontient un auto-test dansif __name__ == "__main__". On peut l’exécuter seul pour le tester, et il ne « fait pas de désordre » quand il est importé.main.pyest enveloppé dansmain()+ la condition. Si quelqu’un l’importe à l’avenir, rien ne s’exécutera tout seul.
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.
Erreur courante
Erreur 1 : écrire "main" au lieu de "__main__".
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 :
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.
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.
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 ?
Afficher la solution
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.
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.
Afficher la solution
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.
Exercice 3: Qu’est-ce que name ?
Dans le fichier greetings.py :
def hello(name):
return "Hello, " + name + "!"
print("__name__ is", __name__)Qu’est-ce qui sera affiché dans chaque cas ?
- Vous lancez
python greetings.py. - Vous lancez
main.py, qui contientimport greetings.
Afficher la solution
- En exécution directe :
__name__ is __main__. - À 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.
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) :
quick self test:
Hello, Tester!Afficher la solution
def hello(name):
return "Hello, " + name + "!"
if __name__ == "__main__":
print("quick self test:")
print(hello("Tester"))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.
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.
add(2, 3)
mean([10, 20, 30])
maximum([10, 20, 30])Résultat attendu :
add: 5
mean: 20.0
maximum: 30Afficher la solution
math_project/
├── main.py
└── mathtools/
├── __init__.py
├── basic.py
└── stats.pymathtools/basic.py
def add(a, b):
return a + bmathtools/stats.py
def mean(numbers):
return sum(numbers) / len(numbers)
def maximum(numbers):
return max(numbers)main.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]))Résumé
- Package = un dossier de modules, avec un
__init__.pyqui 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.