D'un fichier à un dossier de modules
Un seul fichier ne suffit plus quand un projet grandit. Un paquet (package) est un dossier de modules que Python peut importer comme un tout. Marquez le dossier avec un fichier nommé __init__.py (il peut être vide) et chaque fichier .py à l'intérieur devient un module du paquet, désigné avec des points : quant/pricing.py est quant.pricing, et quant/data/loaders.py est quant.data.loaders. Les dossiers à l'intérieur d'un paquet sont des sous-paquets et ont besoin de leur propre __init__.py.
# A small project on disk
#
# quant-tools/
# pyproject.toml
# README.md
# quant/
# __init__.py
# pricing.py
# data/
# __init__.py
# loaders.py
# tests/
# test_pricing.py
# quant/pricing.py
def mid(bid, ask):
return (bid + ask) / 2
# anywhere else in the project
from quant.pricing import mid
import quant.data.loaders
print(mid(184.0, 185.0)) # 184.5Une organisation qui passe à l'échelle
Gardez le code importable dans un dossier de paquet portant le nom du projet, et tout le reste à côté, pas dedans : tests dans tests/, un README.md qui explique le projet, un pyproject.toml qui le nomme et liste ses dépendances, et données ou notebooks dans leurs propres dossiers. Les fichiers de test s'appellent test_*.py pour que des outils comme pytest les trouvent. Des modules petits et ciblés aux noms clairs (pricing.py, loaders.py) sont plus faciles à tester et à réutiliser qu'un long utils.py.
# pyproject.toml (names the project and its dependencies)
#
# [project]
# name = "quant-tools"
# version = "0.1.0"
# requires-python = ">=3.11"
# dependencies = ["numpy"]
# tests/test_pricing.py: found automatically because of the name
from quant.pricing import mid
def test_mid():
assert mid(184.0, 185.0) == 184.5Importer à l'intérieur d'un paquet
À l'intérieur du paquet, on peut importer par chemin complet depuis le haut (from quant.pricing import mid, import absolu) ou relativement au module courant (from .pricing import mid, un point pour le même paquet, deux pour son parent, import relatif). Les imports absolus sont en général plus clairs. __init__.py s'exécute au premier import du paquet : il peut donc réexporter les noms utiles (from .pricing import mid), et import quant; quant.mid(...) fonctionne. Lancez le code d'un paquet avec python -m quant.data.loaders, pas python quant/data/loaders.py, pour que les noms avec points se résolvent.
# quant/__init__.py: runs on the first "import quant"
from .pricing import mid # relative: same package
__all__ = ["mid"] # what "from quant import *" would give
# quant/data/loaders.py
from quant.pricing import mid # absolute: always starts at the top
from .. import pricing # relative: two dots = the parent package
# In a terminal, from the project folder:
# python -m quant.data.loaders (runs the module as part of the package)
# pytest -q (finds tests/test_*.py)# A file path and its module name are two spellings of one thing
path = "quant/data/loaders.py"
name = path[:-3].replace("/", ".")
print(name) # quant.data.loaders
print(name.split(".")[0]) # quant (the top-level package)
# __init__.py stands for its folder
print("quant/__init__.py"[:-3].replace("/", ".")) # quant.__init__ -> the package is "quant"__init__.py (un « paquet d'espace de noms »), mais les outils, lanceurs de tests et installateurs se comportent alors de façon incohérente, et des imports qui marchaient chez vous échouent ailleurs. Donnez un __init__.py à chaque dossier de paquet. Le lab vous demande de trouver les dossiers qui contiennent du code sans en avoir.- Un paquet est un dossier avec
__init__.py;quant/data/loaders.pyest le modulequant.data.loaders. - Gardez le code dans le paquet ; tests, README,
pyproject.tomlet données à côté. - Préférez les imports absolus ; lancez le code d'un paquet avec
python -m package.module.
À suivre : environnements virtuels et pip, pour que chaque projet ait exactement les bibliothèques dont il a besoin.
Lab : ce qui est noté
- Transformer un chemin de fichier en nom de module avec
module_name25 pts - Lister les modules importables d'un projet avec
find_modules25 pts - Repérer les dossiers sans __init__.py avec
missing_init25 pts - Écrire l'instruction d'import d'un module avec
import_line25 pts