Définition de __all__ en Python
En Python, __all__ est une variable spéciale définie au niveau d'un module. Il s'agit d'une list de chaînes de caractères qui indique explicitement quels noms (fonctions, classes, variables) doivent être exportés lorsqu'un utilisateur effectue un from module import *. C'est un mécanisme fondamental pour contrôler l'interface publique de vos modules et packages.
Si vous souhaitez maîtriser Python en profondeur, y compris la structuration de projets professionnels, notre formation complète sur Python vous guidera pas à pas dans l'apprentissage de ces concepts essentiels.
Sans __all__, l'instruction from module import * importe tous les noms publics du module, c'est-à-dire tous les noms qui ne commencent pas par un underscore (_). En définissant __all__, vous prenez le contrôle total sur ce qui est exporté, rendant votre code plus prévisible et mieux organisé.
Syntaxe et fonctionnement de base
La syntaxe de __all__ est très simple : il suffit de déclarer une list de chaînes de caractères au début de votre module, chaque chaîne correspondant au nom d'un objet que vous souhaitez rendre accessible via import *.
Déclaration basique
# mon_module.py
__all__ = ["fonction_publique", "MaClasse", "CONSTANTE"]
def fonction_publique():
"""Cette fonction sera exportée."""
return "Je suis publique"
def _fonction_privee():
"""Cette fonction ne sera pas exportée (underscore)."""
return "Je suis privée"
def fonction_helper():
"""Cette fonction ne sera PAS exportée malgré l'absence d'underscore."""
return "Je suis un helper interne"
class MaClasse:
"""Cette classe sera exportée."""
pass
class ClasseInterne:
"""Cette classe ne sera PAS exportée."""
pass
CONSTANTE = 42
AUTRE_VARIABLE = 100 # Non exportée
Dans cet exemple, seuls fonction_publique, MaClasse et CONSTANTE seront importés lorsqu'un autre fichier exécutera from mon_module import *. Les éléments fonction_helper, ClasseInterne et AUTRE_VARIABLE ne seront pas inclus dans l'import wildcard, même s'ils ne commencent pas par un underscore.
Comportement avec et sans __all__
Voici un comparatif pour bien comprendre la différence :
# === Sans __all__ ===
# utils.py
def calculer():
pass
def _interne():
pass
def formater():
pass
# from utils import * → importe : calculer, formater
# (_interne est exclu car commence par _)
# === Avec __all__ ===
# utils.py
__all__ = ["calculer"]
def calculer():
pass
def _interne():
pass
def formater():
pass
# from utils import * → importe uniquement : calculer
# (formater est exclu même sans underscore)
Important : __all__ n'empêche pas l'import explicite. Même si formater n'est pas dans __all__, vous pouvez toujours écrire from utils import formater. La variable __all__ contrôle uniquement le comportement de import *.
Utilisation dans les packages
L'un des usages les plus puissants de __all__ se trouve dans les fichiers __init__.py des packages Python. Cela vous permet de définir une API publique propre pour l'ensemble de votre package.
Structure d'un package avec __all__
# Structure du package :
# mon_package/
# ├── __init__.py
# ├── module_a.py
# ├── module_b.py
# └── _module_interne.py
# === module_a.py ===
__all__ = ["ClasseA", "fonction_a"]
class ClasseA:
def saluer(self):
return "Bonjour depuis ClasseA"
def fonction_a():
return "Résultat de fonction_a"
def _helper_a():
return "Helper interne"
# === module_b.py ===
__all__ = ["ClasseB"]
class ClasseB:
def calculer(self):
return 42
def fonction_b_interne():
return "Non exportée"
Fichier __init__.py du package
# === __init__.py ===
from .module_a import ClasseA, fonction_a
from .module_b import ClasseB
__all__ = ["ClasseA", "fonction_a", "ClasseB"]
Grâce à cette configuration, un utilisateur de votre package peut écrire simplement :
# Utilisation du package
from mon_package import *
# Seuls ClasseA, fonction_a et ClasseB sont disponibles
obj = ClasseA()
print(obj.saluer()) # Bonjour depuis ClasseA
print(fonction_a()) # Résultat de fonction_a
calc = ClasseB()
print(calc.calculer()) # 42
Attention : Si vous ne définissez pas __all__ dans un fichier __init__.py, l'instruction from package import * n'importera rien du tout (ou uniquement ce qui est explicitement défini dans __init__.py). C'est une source fréquente de confusion pour les débutants.
Exemples pratiques avancés
Construction dynamique de __all__
Vous pouvez construire __all__ de manière dynamique, ce qui est particulièrement utile dans les grands modules :
# api.py - Construction dynamique de __all__
import inspect
import sys
__all__ = []
def export(obj):
"""Décorateur pour marquer une fonction/classe comme exportée."""
__all__.append(obj.__name__)
return obj
@export
def creer_utilisateur(nom, email):
"""Crée un nouvel utilisateur."""
return {"nom": nom, "email": email}
@export
def supprimer_utilisateur(user_id):
"""Supprime un utilisateur par son identifiant."""
return f"Utilisateur {user_id} supprimé"
def _valider_email(email):
"""Fonction interne de validation."""
return "@" in email
@export
class GestionnaireUtilisateurs:
"""Classe principale de gestion des utilisateurs."""
def __init__(self):
self.utilisateurs = []
def ajouter(self, utilisateur):
self.utilisateurs.append(utilisateur)
# À ce stade, __all__ contient automatiquement :
# ["creer_utilisateur", "supprimer_utilisateur", "GestionnaireUtilisateurs"]
print(__all__)
Ce pattern avec un décorateur @export est élégant car il permet de marquer chaque élément comme public directement à l'endroit où il est défini, plutôt que de maintenir une list séparée en haut du fichier.
__all__ avec des réexportations
Un cas courant consiste à réexporter des éléments importés depuis d'autres modules :
# bibliotheque/__init__.py
# Réexportation contrôlée depuis plusieurs sous-modules
from .connexion import Connexion, creer_connexion
from .requetes import executer_requete, RequeteSQL
from .resultats import Resultat, formater_resultat
from .exceptions import (
ErreurConnexion,
ErreurRequete,
ErreurTimeout,
)
__all__ = [
# Connexion
"Connexion",
"creer_connexion",
# Requêtes
"executer_requete",
"RequeteSQL",
# Résultats
"Resultat",
"formater_resultat",
# Exceptions
"ErreurConnexion",
"ErreurRequete",
"ErreurTimeout",
]
Cette approche est très courante dans les bibliothèques Python professionnelles. Elle permet de fournir une API plate et intuitive tout en conservant une organisation interne modulaire.
Vérification de __all__ avec les outils de linting
Vous pouvez écrire un test simple pour vérifier que tous les éléments listés dans __all__ existent réellement dans le module :
# test_exports.py
import mon_module
def test_all_exports_existent():
"""Vérifie que chaque nom dans __all__ existe dans le module."""
for nom in mon_module.__all__:
assert hasattr(mon_module, nom), (
f"'{nom}' est listé dans __all__ mais n'existe pas dans le module"
)
def test_all_est_une_liste():
"""Vérifie que __all__ est bien une liste de chaînes."""
assert isinstance(mon_module.__all__, list)
for element in mon_module.__all__:
assert isinstance(element, str), (
f"Chaque élément de __all__ doit être une chaîne, trouvé : {type(element)}"
)
test_all_exports_existent()
test_all_est_une_liste()
print("Tous les tests passent !")
__all__ et les autres mécanismes d'encapsulation
Il est important de comprendre comment __all__ s'articule avec les autres conventions de Python pour gérer la visibilité des noms :
| Mécanisme | Syntaxe | Effet sur import * | Effet sur l'import explicite |
|---|---|---|---|
__all__ | __all__ = ["nom"] | Seuls les noms listés sont importés | Aucun effet |
| Underscore simple | _nom | Exclu (sans __all__) | Aucun effet |
| Double underscore | __nom | Exclu (name mangling dans les classes) | Accessible via _Classe__nom |
| Dunder | __nom__ | Inclus (sans __all__) | Aucun effet |
Bon à savoir : Lorsque __all__ est défini, il a la priorité absolue. Même les noms commençant par un underscore seront exportés s'ils figurent dans la liste __all__. Inversement, un nom public sera exclu s'il n'y figure pas.
Bonnes pratiques
Voici les recommandations essentielles pour utiliser __all__ efficacement dans vos projets Python :
1. Toujours définir __all__ dans les modules publics
Si votre module est destiné à être importé par d'autres développeurs, définissez systématiquement __all__. Cela documente clairement l'API publique et évite les fuites accidentelles d'éléments internes.
# ✅ Bonne pratique
__all__ = ["traiter_donnees", "valider_entree", "FormulaireDonnees"]
def traiter_donnees(data):
pass
def valider_entree(entree):
pass
class FormulaireDonnees:
pass
def _nettoyer(texte): # Interne
pass
2. Placer __all__ en haut du module
Placez toujours __all__ juste après les imports et la docstring du module. Cela permet à quiconque lisant votre code de comprendre immédiatement l'interface publique :
"""Module de gestion des paiements."""
import json
from datetime import datetime
__all__ = [
"Paiement",
"traiter_paiement",
"rembourser",
"ErreurPaiement",
]
# ... reste du code
3. Utiliser une liste plutôt qu'un tuple
Bien que Python accepte un tuple pour __all__, la convention est d'utiliser une list. Cela facilite la modification et reste cohérent avec la PEP 8 :
# ✅ Recommandé : utiliser une liste
__all__ = ["element_a", "element_b", "element_c"]
# ❌ Moins conventionnel : utiliser un tuple
__all__ = ("element_a", "element_b", "element_c")
4. Garder __all__ synchronisé
L'un des pièges les plus courants est d'ajouter une nouvelle fonction publique au module sans mettre à jour __all__. Pensez à vérifier régulièrement la cohérence, ou utilisez le pattern du décorateur @export présenté plus haut.
5. Éviter import * dans le code de production
Même si __all__ rend import * plus sûr, privilégiez les imports explicites dans votre code de production :
# ❌ À éviter en production
from mon_module import *
# ✅ Préférez l'import explicite
from mon_module import fonction_publique, MaClasse
L'instruction import * reste acceptable dans les fichiers __init__.py pour regrouper les exports d'un package, ou dans des sessions interactives et des notebooks Jupyter.
Cas d'utilisation concrets
Bibliothèques populaires utilisant __all__
De nombreuses bibliothèques Python célèbres utilisent __all__ pour structurer leur API. Par exemple, si vous regardez le code source de os, json ou collections, vous trouverez des définitions de __all__. Vous pouvez d'ailleurs inspecter cette variable directement :
import json
import os
# Voir les exports publics d'un module
print(json.__all__)
# ['dump', 'dumps', 'load', 'loads', 'JSONDecoder', 'JSONDecodeError', 'JSONEncoder']
print(len(os.__all__)) # Nombre d'éléments exportés par os
# Vérifier si un module définit __all__
import math
print(hasattr(math, '__all__')) # False - math ne définit pas __all__
Utilisation avec les type checkers
Les outils comme mypy et pyright utilisent __all__ pour déterminer les symboles publics d'un module. C'est donc important non seulement pour le runtime, mais aussi pour l'analyse statique du code :
# types_utils.py
from typing import TypeVar, Generic
__all__ = ["Identifiant", "Conteneur"]
T = TypeVar("T") # Non exporté
type Identifiant = int | str
class Conteneur(Generic[T]):
def __init__(self, valeur: T) -> None:
self.valeur = valeur
def obtenir(self) -> T:
return self.valeur
Questions fréquentes
Que se passe-t-il si __all__ contient un nom qui n'existe pas dans le module ?
Python lèvera une AttributeError au moment où un utilisateur exécutera from module import *. Le nom introuvable provoquera une erreur explicite. C'est pourquoi il est crucial de garder __all__ synchronisé avec le contenu réel du module. Nous vous recommandons d'écrire des tests automatisés pour vérifier cette cohérence.
__all__ affecte-t-il la fonction dir() ?
Non, __all__ n'affecte pas dir(). La fonction len ou dir() appliquée à un module listera toujours tous les attributs du module, qu'ils soient dans __all__ ou non. La variable __all__ contrôle exclusivement le comportement de from module import * et sert de documentation pour les outils d'analyse statique.
Peut-on utiliser __all__ dans un script principal ?
Techniquement oui, mais cela n'a aucune utilité pratique. La variable __all__ n'a de sens que dans un module destiné à être importé par d'autres fichiers. Si votre fichier est le script principal (avec if __name__ == "__main__"), personne ne fera import * dessus, donc __all__ sera simplement ignoré.
Comment apprendre à bien structurer ses modules Python ?
La structuration de modules et packages est une compétence essentielle pour tout développeur Python. Comprendre __all__, les imports relatifs, les fichiers __init__.py et les conventions de nommage demande de la pratique. Notre formation dédiée à Python couvre en détail la création de modules propres, la gestion des packages et toutes les bonnes pratiques pour écrire du code professionnel et maintenable.