__init__.py vide ou pas

bonjour,

Sur un projet existant que je rejoins, le fichier __init__.py contient l’import des différents modules.

D’après mes ressources, il serait préférable de laisser le __init__.py vide :

  1. C’est ce que l’on trouve dans le Hitchhiker’s Guide

  2. Et dans cette vidéo de Brandon Rhodes à la conférence code::dive 2019 : When Python Practices Go Wrong (44e minute)

Voici ce qu’il dit :

__init__.py is a cost in the way of all the other modules that you might want to import.

Worse yet, for convenience, some __init__.py files import all their package’s module. That’s ruining the ability to cherry pick one thing you want. You can’t touch the package without waiting to load everything inside of it.

My advice, which is not what everyone would say, is to keep __init__.py empty of code

I do understand that my users don’t want to have to import things manually from all dozen of my modules, so I have “an API module” that import all of the important classes so that you can say :

from skyfield.api import xxx, yyy, zzz

J’ai retrouvé le fichier où il met ses imports : python-skyfield/skyfield/api.py at master · skyfielders/python-skyfield · GitHub

  1. Cependant, un article très récent de RealPython propose exactement l’inverse. Est-ce qu’il y a des choses qui auraient changé ?

Quel est votre avis ?

Je préfèrerai partir sur un __init__.py vide et cela m’intéresse de mettre les éventuels imports dans un fichier à part mais j’ai peur que ça me mette le bazar pour la documentation Sphinx (et sphinx-apidoc mais je vois un exclude pattern)

Merci d’avance pour vos conseils.

Françoise

Salut,

Mon avis est qu’il faut que l’__init__ expose ce qui est nécessaire / intéressant pour le module.
Ça peut être un ensemble de classes qu’il faudra de toute façon importer pour utiliser correctement le module, ou des fonctions points d’entrée (définies dans l’__init__ ou importées).

Je ne comprends pas et ne souscris pas à l’idée que ce fichier devrait toujours être vide.
Je n’imagine pas faire un import collections et me retrouver avec un module vide par exemple.

1 « J'aime »

J’ai tendance à les avoir vides, mais pas toujours. Je ferai bien une réponse de Normand : ça dépend donc des besoins :wink:

C’est peut-être caricatural de parler d’un __init__.py vide.

Mais ce que j’ai retenu des ressources citées, c’était de minimiser le code que l’on y met et de limiter les import car le __init__.py va être ouvert et exécuté à chaque “traversée” du package et donc cela peut être coûteux.

Oui, ce n’est clairement pas fait pour coder dedans. une chaîne de __version__, quelques imports OK, mais pas de “vrai” code. Pas question de tout coder dans le __init__.

et de limiter les import car le __init__.py va être ouvert et exécuté à chaque “traversée” du package et donc cela peut être coûteux.

Non, le corps du __init__ ne sera exécuté qu’une seule fois et mis en cache comme tous les autres modules dans sys.modules. D’ailleurs __init__.py est un module, comme un autre (une instance de la classe module).

Tu peux t’en convaincre facilement en ajoutant un print dans ton __init__.py : il n’apparaîtra qu’une fois.

Un avantage à mettre des import dans le __init__.py est de simplifier l’import (pour les utilisateurs du paquet) d’objets importants (API publique, exceptions).

Au lieu de taper from ton_paquet.exceptions import TonPaquetError, si tu importe TonPaquetError dans ton __init__.py tes utilisateurs pourront se contenter de from ton_paquet import TonPaquetError, c’est plus court.

Théoriquement ça rend aussi possible de réorganiser la hiérarchie interne de ton paquet sans casser les imports qu’ont fait les utilisateurs de ton paquet : il suffit de réparer les imports du __init__.py. Mais en pratique on sait bien qu’au moins un utilisateur a fait un import explicite et que pour lui la réorganisation va casser…

1 « J'aime »

Théoriquement ça rend aussi possible de réorganiser la hiérarchie interne de ton paquet sans casser les imports qu’ont fait les utilisateurs de ton paquet

Ça me rappelle l’excellente (mais vieillotte) vidéo que je recommande très régulièrement aux gens qui viennent d’un autre langage que Python, genre Java :

Dans la miniature, il part d’un « faux » code visiblement écrit par quelqu’un qui a fait du Java, avec des imports dans tous les sens, et avec une hiérarchie profonde. Et montre comment passer à un code pythonique, en simplifiant l’interface utilisateur (et entre autres en simplifiant les imports).

Et d’ailleurs je recommande fortement toutes les vidéos de Raymond Hettinger : PyVideo.org

1 « J'aime »

Merci pour vos retours et pour la vidéo de Raymond Hettinger que je vais essayer de regarder.

Je comprends qu’il est compliqué de modifier l’existant, mais dans mon cas c’est un code plutôt récent avec peu d’utilisateurs. Je vais donc en discuter avec l’auteur qui est dans mon labo.

J’ai trouvé une vidéo de la PyConAU 2019, No time to idle about: Profiling import time in Python qui m’a été utile

J’ai pu faire quelques modifications que je vais soumettre à l’auteur et on verra si on les garde ou non.

À propos du profiling d’import, 2 ressources complémentaires :

Encore merci à vous et bonne fin de semaine

Quel interès de creer un fichier __init__.py si c’est pour le laisser vide? Je sais pas car je ne l’utilise pas (ou pas encore) mais a quoi ca servirais du coup? C’est clairement pas comme un fichier vide .nomedia qui empèche l’indexation du dossier ou il se trouve dans certains systèmes donc a mon avis si tu mêt rien dedans pas la peine de le créer (sauf erreur). Après si tu importe tout le temps les mêmes modules peut-être que les mêttre la au lieux de les mêttre dans chaque fichiers qui les utilise peut être bien niveau perf (sauf si les modules ne se chargent qu’une seule fois même si importés pleins de fois ce qui est plus que probable).

Ce fichier (même vide) sert à signaler à Python qu’il s’agit d’un paquet (et non d’un namespace-package), dans le temps il était même je crois nécessaire (puisque les namespace-packages n’existaient pas).

Cela peut être difficile de saisir la différence, donc voilà un exemple qui fonctionne grâce à une manipulation du PYTHONPATH (pour simplifier les choses, mais ça pourrait être reproduit en utilisant des répertoires d’installation).

Prenons la hiérarchie de fichiers / répertoires suivante :

.
├── package
│   └── foo.py
└── subdirectory
    └── package
        └── spam.py

Aucun fichier __init__.py à l’horizon, il s’agit donc de namespace-packages, et les deux paquets partageant le même nom (package), on se retrouve à les fusionner en un paquet contenant deux modules foo et spam.
Si je lance Python depuis le répertoire courant (en modifiant le PYTHONPATH comme annoncé pour que les paquets définis dans subdirectory soient accessibles), je peux faire :

% PYTHONPATH=subdirectory python
Python 3.13.5 (main, Jun 21 2025, 09:35:00) [GCC 15.1.1 20250425] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from package.foo import foo
>>> from package.spam import spam
>>> foo()
'foo'
>>> spam()
'spam'

En revanche, si j’ajoute maintenant des fichiers __init__.py vides dans mes répertoires package :

.
├── package
│   ├── foo.py
│   └── __init__.py
└── subdirectory
    └── package
        ├── __init__.py
        └── spam.py

Je me retrouve à définir un « vrai » paquet Python qui ne peut pas être rouvert ailleurs, et donc qui ne contient qu’un module foo (mais il pourrait ne contenir qu’un module spam suivant l’ordre de priorité du PYTHONPATH, dans mon cas le répertoire courant passe avant le répertoire subdirectory qui est ajouté seulement après)

% PYTHONPATH=subdirectory python
Python 3.13.5 (main, Jun 21 2025, 09:35:00) [GCC 15.1.1 20250425] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from package.foo import foo
>>> from package.spam import spam
Traceback (most recent call last):
  File "<python-input-1>", line 1, in <module>
    from package.spam import spam
ModuleNotFoundError: No module named 'package.spam'

On peut ensuite se demander pourquoi ces namespace-packages existent, et la réponse est que ça permet de gérer des systèmes de plugins par exemple (chaque paquet allant rajouter les modules nécessaires dans un espace de noms commun)

Quelle recommandation on peut en tirer ? Toujours mettre un __init__.py dans les répertoires des paquets Python, à moins de vouloir explicitement créer un namespace-package.

5 « J'aime »

Donc ca évite d’avoir des problèmes a cause de 2 fichiers qui ont le meme nom dans 2 bibliothèques qui n’ont rien a voire (genre une variable windows qui serait a la fois dans pygame et wx)?..

Non pas vraiment (car il y a peu de chances pour que pygame et wx partagent un nom de module commun) mais ça peut produire ce genre de problème oui, et ça oblige à continuer de parcourir la liste des répertoires pour en découvrir d’autres correspondant au même nom.

Le dernier commit sur tuna a plus de 2 ans.
La dernière version sur PyPI est de décembre 2021.

Tuna ne fonctionne pas avec des fichiers de cProfile en Python 3.12, retour à SnakeViz

@fcodvpt concernant les fichiers __init__.py, pour faire une réponse de faux-normand, ça dépend de :

  • si tu considères que les gens vont se débrouiller à récupérer les différents éléments dont iels ont besoin dans l’arborescence de ton package, tu peux laisser tous tes __init__.py vide (c’est ce que dit Brandon Rhodes d’après la citation)
  • si tu préfères définir une jolie “interface” pour ton package, avec l’essentiel dedans pour que les gens n’aient pas à chercher, alors remplis ton __init__.py (et éventuellement son __all__ si tu veux permettre les sulfureux from truc import *)
  • si t’as la flemme, ne mets rien dans les __init__.py et ça marchera quand même
  • si t’as vraiment beaucoup la flemme, ne mets même pas les __init__.py et chaque import sera légèrement plus lent (exercice à la maison : profiler le temps d’import d’un package “normal” versus un “namespace package”), et ton arborescence moins claire/Pythonique