En Python, un package d'espace de noms vous permet de répartir le code Python entre plusieurs projets. Cela est utile lorsque vous souhaitez publier des bibliothèques associées sous forme de téléchargements séparés. Par exemple, avec les répertoires Package-1
et Package-2
dans PYTHONPATH
,
Package-1/namespace/__init__.py
Package-1/namespace/module1/__init__.py
Package-2/namespace/__init__.py
Package-2/namespace/module2/__init__.py
l'utilisateur final peut import namespace.module1
et import namespace.module2
.
Quelle est la meilleure façon de définir un package d'espace de noms afin que plusieurs produits Python puissent définir des modules dans cet espace de noms?
TL; DR:
Sur Python 3.3 vous n'avez rien à faire, il suffit de ne pas mettre de __init__.py
Dans les répertoires de votre package d'espace de noms et cela fonctionnera. Sur la version antérieure à 3.3, choisissez la solution pkgutil.extend_path()
sur la solution pkg_resources.declare_namespace()
, car elle est évolutive et déjà compatible avec les packages d'espace de noms implicites.
Python 3.3 introduit des packages d'espace de noms implicites, voir PEP 42 .
Cela signifie qu'il existe maintenant trois types d'objets qui peuvent être créés par un import foo
:
foo.py
foo
contenant un fichier __init__.py
foo
sans aucun fichier __init__.py
Les packages sont aussi des modules, mais ici je veux dire "module non-package" quand je dis "module".
Tout d'abord, il recherche dans sys.path
Un module ou un package standard. S'il réussit, il arrête la recherche et crée et initialise le module ou le package. S'il n'a trouvé aucun module ou package normal, mais qu'il a trouvé au moins un répertoire, il crée et initialise un package d'espace de noms.
Les modules et les packages standard ont __file__
Défini sur le fichier .py
À partir duquel ils ont été créés. Les packages standard et d'espace de noms ont __path__
Défini sur le ou les répertoires à partir desquels ils ont été créés.
Lorsque vous faites import foo.bar
, La recherche ci-dessus se produit d'abord pour foo
, puis si un package a été trouvé, la recherche de bar
se fait avec foo.__path__
Comme chemin de recherche au lieu de sys.path
. Si foo.bar
Est trouvé, foo
et foo.bar
Sont créés et initialisés.
Alors, comment se mélangent les packages réguliers et les packages d'espace de noms? Normalement, ce n'est pas le cas, mais l'ancienne méthode de package d'espace de noms explicite pkgutil
a été étendue pour inclure les packages d'espace de noms implicites.
Si vous avez un package régulier existant qui a un __init__.py
Comme ceci:
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
... le comportement hérité consiste à ajouter tout autre paquetage régulier sur le chemin recherché à son __path__
. Mais dans Python 3.3, il ajoute également des packages d'espace de noms.
Vous pouvez donc avoir la structure de répertoires suivante:
├── path1
│ └── package
│ ├── __init__.py
│ └── foo.py
├── path2
│ └── package
│ └── bar.py
└── path3
└── package
├── __init__.py
└── baz.py
... et tant que les deux __init__.py
ont les lignes extend_path
(et path1
, path2
et path3
sont dans votre sys.path
) import package.foo
, import package.bar
Et import package.baz
Fonctionneront tous.
pkg_resources.declare_namespace(__name__)
n'a pas été mis à jour pour inclure les packages d'espace de noms implicites.
Il existe un module standard, appelé pkgutil , avec lequel vous pouvez "ajouter" des modules à un espace de noms donné.
Avec la structure de répertoires que vous avez fournie:
Package-1/namespace/__init__.py
Package-1/namespace/module1/__init__.py
Package-2/namespace/__init__.py
Package-2/namespace/module2/__init__.py
Vous devez mettre ces deux lignes dans les deux Package-1/namespace/__init__.py
et Package-2/namespace/__init__.py
(*):
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
(* puisque -sauf si vous indiquez une dépendance entre eux- vous ne savez pas lequel sera reconnu en premier - voir PEP 42 pour plus d'informations)
Comme le dit documentation :
Cela ajoutera à
__path__
tous les sous-répertoires des répertoires sursys.path
nommé d'après le package.
Désormais, vous devriez pouvoir distribuer ces deux packages indépendamment.
Cette section devrait être assez explicite.
En bref, mettez le code de l'espace de noms dans __init__.py
, mise à jour setup.py
pour déclarer un espace de noms, et vous êtes libre de partir.
C'est une vieille question, mais quelqu'un a récemment commenté sur mon blog que ma publication sur les packages d'espace de noms était toujours pertinente, alors j'ai pensé y faire un lien ici car il fournit un exemple pratique de la façon de le faire:
Cela renvoie à cet article pour les principales tripes de ce qui se passe:
http://www.siafoo.net/article/77#multiple-distributions-one-virtual-package
L'astuce __import__("pkg_resources").declare_namespace(__name__)
conduit à peu près la gestion des plugins dans TiddlyWeb et semble jusqu'à présent fonctionner.