web-dev-qa-db-fra.com

Comment créer un package d'espace de noms en Python?

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?

128
joeforker

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:

  • Un module représenté par un fichier foo.py
  • Un package standard, représenté par un répertoire foo contenant un fichier __init__.py
  • Un package d'espace de noms, représenté par un ou plusieurs répertoires 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.

63
clacke

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 sur sys.path nommé d'après le package.

Désormais, vous devriez pouvoir distribuer ces deux packages indépendamment.

78
Mike Hordecki

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.

5
iElectric

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:

https://web.archive.org/web/20150425043954/http://cdent.tumblr.com/post/216241761/python-namespace-packages-for-tiddlyweb

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.

2
cdent