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


141

En Python, un package d'espace de noms vous permet de diffuser du code Python entre plusieurs projets. Ceci 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-1et Package-2in 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.module1et 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?


5
Il me semble que module1 et module2 sont en fait des sous-packages plutôt que des modules. Si je comprends bien, un module est essentiellement un fichier unique. Peut-être que subpkg1 et subpkg2 auraient plus de sens en tant que noms?
Alan

Réponses:


79

TL; DR:

Sur Python 3.3, vous n'avez rien à faire, n'en mettez tout simplement pas __init__.pydans les répertoires de vos packages d'espace de noms et cela fonctionnera. Sur la version antérieure à la version 3.3, choisissez la pkgutil.extend_path()solution plutôt que pkg_resources.declare_namespace()celle, car elle est à l'épreuve du temps et déjà compatible avec les packages d'espace de noms implicites.


Python 3.3 introduit les packages d'espace de noms implicites, voir PEP 420 .

Cela signifie qu'il existe désormais trois types d'objets qui peuvent être créés par un import foo:

  • Un module représenté par un foo.pyfichier
  • Un package régulier, représenté par un répertoire foocontenant un __init__.pyfichier
  • Un package d'espace de noms, représenté par un ou plusieurs répertoires foosans aucun __init__.pyfichier

Les packages sont aussi des modules, mais ici je veux dire "module non-package" quand je dis "module".

Il recherche sys.pathd'abord 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 normaux sont __file__définis sur le .pyfichier à partir duquel ils ont été créés. Les packages standard et d'espace de noms sont __path__définis sur le ou les répertoires à partir desquels ils ont été créés.

Lorsque vous le faites import foo.bar, la recherche ci-dessus se produit d'abord pour foo, puis si un package a été trouvé, la recherche barest effectuée avec foo.__path__comme chemin de recherche au lieu de sys.path. Si foo.baron trouve, fooet foo.barsont créés et initialisés.

Alors, comment se mélangent les packages standards et les packages d'espace de noms? Normalement, ce n'est pas le cas, mais l'ancienne pkgutilméthode de package d'espace de noms explicite a été étendue pour inclure des packages d'espace de noms implicites.

Si vous avez un package régulier existant qui a un __init__.pycomme ceci:

from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)

... le comportement hérité consiste à ajouter tous les autres packages réguliers 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__.pyont les extend_pathlignes (et path1, path2et path3sont dans votre sys.path) import package.foo, import package.baret import package.baztout fonctionnera.

pkg_resources.declare_namespace(__name__) n'a pas été mis à jour pour inclure les packages d'espace de noms implicites.


2
Qu'en est-il de setuptools? Dois-je utiliser l' namespace_packagesoption? Et la __import__('pkg_resources').declare_namespace(__name__)chose?
kawing-chiu

3
Dois-je ajouter namespace_packages=['package']le setup.py?
Laurent LAPORTE

1
@clacke: Avec namespace_packages=['package'], setup.py ajoutera un namespace_packages.txtdans EGG-INFO. Je ne connais toujours pas les impacts…
Laurent LAPORTE

1
@ kawing-chiu L'avantage de pkg_resources.declare_namespaceover pkgutil.extend_pathest qu'il continuera à surveiller sys.path. De cette façon, si un nouvel élément est ajouté sys.pathaprès le premier chargement d'un package dans l'espace de noms, les packages de l'espace de noms dans ce nouvel élément de chemin peuvent toujours être chargés. (Un avantage de l'utilisation de __import__('pkg_resources')over import pkg_resourcesest que vous ne finissez pas par pkg_resourcesêtre exposé comme my_namespace_pkg.pkg_resources.)
Arthur Tacca

1
@clacke Cela ne fonctionne pas de cette façon (mais cela a le même effet que si c'était le cas). Il gère une liste globale de tous les espaces de noms de packages créés avec cette fonction et surveille sys.path. Lorsqu'il sys.pathchange, il vérifie si cela affecte le __path__de n'importe quel espace de noms, et si c'est le cas, il met à jour ces __path__propriétés.
Arthur Tacca

81

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 devriez 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 - à moins que vous n'indiquiez une dépendance entre eux - vous ne savez pas lequel d'entre eux sera reconnu en premier - voir PEP 420 pour plus d'informations)

Comme la documentation dit :

Cela ajoutera à __path__tous les sous-répertoires des répertoires du package sursys.path nommés d'après le package.

À partir de maintenant, vous devriez pouvoir distribuer ces deux packages indépendamment.


17
Quels sont les avantages et les inconvénients de son utilisation par rapport à import __ ('pkg_resources'). Declare_namespace (__ name )?
joeforker

14
Tout d'abord, __import__est considéré comme un mauvais style dans ce cas, car il peut être facilement remplacé par une instruction d'importation simple. Plus précisément, pkg_resources est une bibliothèque non standard. Il est livré avec setuptools, donc ce n'est pas un problème. Une recherche rapide sur Google révèle que pkgutil a été introduit dans la version 2.5 et que pkg_resources est antérieur. Néanmoins, pkgutil est une solution officiellement reconnue. L'inclusion de pkg_resources a été, en fait, rejetée dans PEP 365.
Mike Hordecki

3
Citation de PEP 382 : L'approche impérative actuelle des packages d'espaces de noms a conduit à plusieurs mécanismes légèrement incompatibles pour fournir des packages d'espaces de noms. Par exemple, pkgutil prend en charge les fichiers * .pkg; setuptools ne le fait pas. De même, setuptools prend en charge l'inspection des fichiers zip et prend en charge l'ajout de parties à sa variable _namespace_packages, contrairement à pkgutil.
Drake Guan

7
Ces deux lignes ne devraient-elles pas être placées dans les deux fichiers: Package-1/namespace/__init__.py et à Package-2/namespace/__init__.py condition que nous ne sachions pas quel répertoire de package est répertorié en premier?
Bula

3
@ChristofferKarlsson oui c'est le but, ça va si vous savez lequel est le premier, mais la vraie question est de savoir si vous pouvez garantir que ce sera le premier dans n'importe quelle situation, c'est à dire pour les autres utilisateurs?
Bula


2

C'est une vieille question, mais quelqu'un a récemment commenté sur mon blog que ma publication sur les packages d'espaces de noms était toujours pertinente, alors j'ai pensé que je ferais un lien vers elle ici car elle 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 principaux tripes de ce qui se passe:

http://www.siafoo.net/article/77#multiple-distributions-one-virtual-package

L' __import__("pkg_resources").declare_namespace(__name__)astuce conduit à peu près à la gestion des plugins dans TiddlyWeb et semble jusqu'à présent fonctionner.


-9

Vous avez vos concepts d'espace de noms Python à l'envers, il n'est pas possible en python de mettre des packages dans des modules. Les packages contiennent des modules et non l'inverse.

Un package Python est simplement un dossier contenant un __init__.pyfichier. Un module est tout autre fichier dans un package (ou directement sur le PYTHONPATH) qui a un.py extension. Donc, dans votre exemple, vous avez deux packages mais aucun module n'est défini. Si vous considérez qu'un package est un dossier de système de fichiers et qu'un module est un fichier, vous voyez pourquoi les packages contiennent des modules et non l'inverse.

Donc, dans votre exemple, en supposant que Package-1 et Package-2 sont des dossiers sur le système de fichiers que vous avez mis sur le chemin Python, vous pouvez avoir les éléments suivants:

Package-1/
  namespace/
  __init__.py
  module1.py
Package-2/
  namespace/
  __init__.py
  module2.py

Vous avez maintenant un package namespaceavec deux modules module1et module2. et à moins que vous n'ayez une bonne raison, vous devriez probablement mettre les modules dans le dossier et n'avoir que cela sur le chemin python comme ci-dessous:

Package-1/
  namespace/
  __init__.py
  module1.py
  module2.py

Je parle de choses comme zope.xoù un tas de packages connexes sont publiés en tant que téléchargements séparés.
joeforker

Ok, mais quel est l'effet que vous essayez d'obtenir. Si les dossiers contenant les packages associés sont tous sur PYTHONPATH, l'interpréteur Python les trouvera pour vous sans effort supplémentaire de votre part.
Tendayi Mawushe

5
Si vous ajoutez à la fois Package-1 et Package-2 à PYTHONPATH, seul Package-1 / namespace / sera vu par Python.
Søren Løvborg
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.