¿Cómo creo un paquete de espacio de nombres en Python?

141

En Python, un paquete de espacio de nombres le permite distribuir el código de Python entre varios proyectos. Esto es útil cuando desea lanzar bibliotecas relacionadas como descargas separadas. Por ejemplo, con los directorios Package-1y Package-2en PYTHONPATH,

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

el usuario final puede import namespace.module1y import namespace.module2.

¿Cuál es la mejor manera de definir un paquete de espacio de nombres para que más de un producto Python pueda definir módulos en ese espacio de nombres?

joeforker
fuente
55
Me parece que module1 y module2 son en realidad subpaquetes en lugar de módulos. Según tengo entendido, un módulo es básicamente un solo archivo. ¿Quizás subpkg1 y subpkg2 tendrían más sentido como nombres?
Alan

Respuestas:

79

TL; DR:

En Python 3.3 no tiene que hacer nada, simplemente no coloque ninguno __init__.pyen sus directorios de paquetes de espacio de nombres y simplemente funcionará. En pre-3.3, elija la pkgutil.extend_path()solución sobre la anterior pkg_resources.declare_namespace(), porque es a prueba de futuro y ya es compatible con paquetes de espacio de nombres implícitos.


Python 3.3 introduce paquetes de espacio de nombres implícitos, ver PEP 420 .

Esto significa que ahora hay tres tipos de objetos que pueden ser creados por un import foo:

  • Un módulo representado por un foo.pyarchivo
  • Un paquete normal, representado por un directorio que foocontiene un __init__.pyarchivo
  • Un paquete de espacio de nombres, representado por uno o más directorios foosin ningún __init__.pyarchivo

Los paquetes también son módulos, pero aquí me refiero a "módulo sin paquete" cuando digo "módulo".

Primero busca sys.pathun módulo o paquete normal. Si tiene éxito, deja de buscar y crea e inicializa el módulo o paquete. Si no encontró ningún módulo o paquete normal, pero encontró al menos un directorio, crea e inicializa un paquete de espacio de nombres.

Los módulos y paquetes regulares se han __file__configurado en el .pyarchivo desde el que se crearon. Los paquetes regulares y de espacio de nombres se han __path__establecido en el directorio o directorios a partir de los cuales fueron creados.

Cuando lo haga import foo.bar, la búsqueda anterior se realiza primero foo, luego, si se encontró un paquete, la búsqueda barse realiza foo.__path__como la ruta de búsqueda en lugar de sys.path. Si foo.barse encuentra fooy foo.barse crea e inicializa.

Entonces, ¿cómo se mezclan los paquetes regulares y los paquetes de espacio de nombres? Normalmente no lo hacen, pero el antiguo pkgutilmétodo de paquete de espacio de nombres explícito se ha ampliado para incluir paquetes de espacio de nombres implícitos.

Si tiene un paquete regular existente que tiene un __init__.pyaspecto como este:

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

... el comportamiento heredado es agregar cualquier otro paquete regular en la ruta buscada __path__. Pero en Python 3.3, también agrega paquetes de espacio de nombres.

Para que pueda tener la siguiente estructura de directorios:

├── path1
   └── package
       ├── __init__.py
       └── foo.py
├── path2
   └── package
       └── bar.py
└── path3
    └── package
        ├── __init__.py
        └── baz.py

... y siempre y cuando los dos __init__.pytengan las extend_pathlíneas (y path1, path2y path3estén en su sys.path) import package.foo, import package.bary import package.baztodos funcionen.

pkg_resources.declare_namespace(__name__) no se ha actualizado para incluir paquetes de espacio de nombres implícitos.

clacke
fuente
2
¿Qué pasa con las herramientas de configuración? ¿Tengo que usar la namespace_packagesopción? Y la __import__('pkg_resources').declare_namespace(__name__)cosa?
kawing-chiu
3
¿Debo agregar namespace_packages=['package']el setup.py?
Laurent LAPORTE
1
@clacke: Con namespace_packages=['package'], setup.py agregará un namespace_packages.txten EGG-INFO. Todavía no conozco los impactos ...
Laurent LAPORTE
1
@ kawing-chiu El beneficio de pkg_resources.declare_namespaceover pkgutil.extend_pathes que continuará monitoreando sys.path. De esa manera, si se agrega un nuevo elemento sys.pathdespués de que un paquete en el espacio de nombres se carga por primera vez, los paquetes en el espacio de nombres en ese nuevo elemento de ruta aún se pueden cargar. (Una ventaja de usar __import__('pkg_resources')más import pkg_resourceses que no terminas pkg_resourcesexpuesto como my_namespace_pkg.pkg_resources.)
Arthur Tacca
1
@clacke No funciona de esa manera (pero tiene el mismo efecto que si lo hiciera). Mantiene una lista global de todos los espacios de nombres de paquetes creados con esa función, y observa sys.path. Cuando sys.pathcambia, verifica si eso afecta el __path__espacio de nombres, y si lo hace, actualiza esas __path__propiedades.
Arthur Tacca el
81

Hay un módulo estándar, llamado pkgutil , con el que puede 'agregar' módulos a un espacio de nombres dado.

Con la estructura de directorios que ha proporcionado:

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

Debe poner esas dos líneas en ambos Package-1/namespace/__init__.pyy Package-2/namespace/__init__.py(*):

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

(* dado que, a menos que establezca una dependencia entre ellos, no sabe cuál de ellos se reconocerá primero; consulte PEP 420 para obtener más información)

Como dice la documentación :

Esto agregará a __path__todos los subdirectorios de directorios sys.pathdel paquete con el nombre del paquete.

A partir de ahora, debería poder distribuir esos dos paquetes de forma independiente.

Mike Hordecki
fuente
17
¿Cuáles son los pros y los contras de usar eso versus import __ ('pkg_resources'). Declare_namespace (__ name )?
joeforker el
14
Primero, __import__se considera un mal estilo en este caso, ya que se puede reemplazar fácilmente con una declaración de importación simple. Más concretamente, pkg_resources es una biblioteca no estándar. Viene con herramientas de configuración, por lo que no es un problema. Google rápido revela que pkgutil se introdujo en 2.5 y pkg_resources es anterior a él. Sin embargo, pkgutil es una solución oficialmente reconocida. La inclusión de pkg_resources fue, de hecho, rechazada en PEP 365.
Mike Hordecki el
3
Cita de PEP 382 : El enfoque imperativo actual de los paquetes de espacios de nombres ha dado lugar a múltiples mecanismos ligeramente incompatibles para proporcionar paquetes de espacios de nombres. Por ejemplo, pkgutil admite archivos * .pkg; setuptools no lo hace. Del mismo modo, setuptools admite la inspección de archivos zip y admite agregar porciones a su variable _namespace_packages, mientras que pkgutil no.
Drake Guan
77
¿No deberían colocarse estas dos líneas en ambos archivos, Package-1/namespace/__init__.py y Package-2/namespace/__init__.py siempre que no sepamos qué paquete de directorios aparece primero?
Bula
3
@ChristofferKarlsson sí, ese es el punto, está bien si sabes cuál es el primero, pero la verdadera pregunta es ¿puedes garantizar que será el primero en cualquier situación, es decir, para otros usuarios?
Bula
5

Esta sección debería explicarse bastante por sí misma.

En resumen, ingrese el código del espacio de nombres __init__.py, actualícelo setup.pypara declarar un espacio de nombres y tendrá libertad para continuar.

iElectric
fuente
9
Siempre debe citar la parte relevante de un enlace, en caso de que el enlace relevante se cierre.
Tinned_Tuna
2

Esta es una vieja pregunta, pero alguien comentó recientemente en mi blog que mi publicación sobre paquetes de espacios de nombres aún era relevante, por lo que pensé en vincularla aquí ya que proporciona un ejemplo práctico de cómo hacerlo:

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

Eso enlaza a este artículo para conocer las principales agallas de lo que está sucediendo:

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

El __import__("pkg_resources").declare_namespace(__name__)truco consiste en impulsar la administración de complementos en TiddlyWeb y hasta ahora parece estar funcionando.

cdent
fuente
-9

Tiene sus conceptos de espacio de nombres de Python al frente, no es posible en python poner paquetes en módulos. Los paquetes contienen módulos, no al revés.

Un paquete de Python es simplemente una carpeta que contiene un __init__.pyarchivo. Un módulo es cualquier otro archivo en un paquete (o directamente en el PYTHONPATH) que tenga una .pyextensión. Entonces, en su ejemplo, tiene dos paquetes pero no hay módulos definidos. Si considera que un paquete es una carpeta del sistema de archivos y un módulo es un archivo, entonces verá por qué los paquetes contienen módulos y no al revés.

Entonces, en su ejemplo, suponiendo que Package-1 y Package-2 son carpetas en el sistema de archivos que ha colocado en la ruta de Python, puede tener lo siguiente:

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

Ahora tiene un paquete namespacecon dos módulos module1y module2. y a menos que tenga una buena razón, probablemente debería poner los módulos en la carpeta y tener solo eso en la ruta de Python como a continuación:

Package-1/
  namespace/
  __init__.py
  module1.py
  module2.py
Tendayi Mawushe
fuente
Estoy hablando de cosas como zope.xdonde se lanzan un montón de paquetes relacionados como descargas separadas.
joeforker el
Ok, pero ¿cuál es el efecto que estás tratando de lograr? Si las carpetas que contienen paquetes relacionados están todas en PYTHONPATH, el intérprete de Python las encontrará sin ningún esfuerzo adicional de su parte.
Tendayi Mawushe el
55
Si agrega Package-1 y Package-2 a PYTHONPATH, Python solo verá Package-1 / namespace /.
Søren Løvborg