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

En Python, un paquete de espacio de nombres le permite difundir el código de Python entre varios proyectos. Esto es útil cuando desea liberar bibliotecas relacionadas como descargas separadas. Por ejemplo, con los directorios Package-1 y Package-2 en 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.module1 e import namespace.module2 .

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

TL; DR:

En Python 3.3 no tienes que hacer nada, simplemente no pongas __init__.py en tus directorios de paquetes de espacio de nombres y simplemente funcionará. En la pkgutil.extend_path() anterior a la 3.3, elija la solución pkgutil.extend_path() sobre la pkg_resources.declare_namespace() , porque está pkg_resources.declare_namespace() el futuro y ya es compatible con los 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 archivo foo.py
  • Un paquete regular, representado por un directorio foo contiene un archivo __init__.py
  • Un paquete de espacio de nombres, representado por uno o más directorios foo sin ningún archivo __init__.py

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

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

Los módulos y paquetes regulares tienen __file__ establecido en el archivo .py el que se crearon. Los paquetes regulares y de espacio de nombres tienen __path__ establecido en el directorio o directorios desde los que se crearon.

Cuando import foo.bar , la búsqueda anterior se realiza primero para foo , luego, si se encuentra un paquete, la búsqueda de la bar se realiza con foo.__path__ como la ruta de búsqueda en lugar de sys.path . Si se encuentra foo.bar , foo y foo.bar se crean e inicializan.

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

Si tiene un paquete regular existente que tiene un __init__.py 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 a su __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 mientras los dos __init__.py tengan las líneas extend_path (y path1 , path2 y path3 están en su sys.path ) import package.foo , import package.bar e import package.baz funcionarán.

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

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

Con la estructura de directorios que has proporcionado:

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

Debe colocar esas dos líneas en el Package-1/namespace/__init__.py y en el Package-2/namespace/__init__.py (*):

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

(* dado que, a menos que indique 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__ del paquete todos los subdirectorios de directorios en sys.path nombrados después del paquete.

De ahora en adelante, deberías poder distribuir esos dos paquetes de manera independiente.

Esta sección debe ser bastante autoexplicativa.

En resumen, ponga el código del espacio de nombres en __init__.py , actualice setup.py para declarar un espacio de nombres, y podrá irse.

Esta es una pregunta antigua, pero alguien comentó recientemente en mi blog que mi publicación sobre los paquetes de espacio de nombres aún era relevante, así que pensé que me vincularía 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

Que enlaza a este artículo para las principales entrañas de lo que está pasando:

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

El __import__("pkg_resources").declare_namespace(__name__) es prácticamente lo que impulsa la administración de complementos en TiddlyWeb y hasta ahora parece estar funcionando.

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

Un paquete de Python es simplemente una carpeta que contiene un archivo __init__.py . Un módulo es cualquier otro archivo en un paquete (o directamente en PYTHONPATH ) que tiene una extensión .py . Entonces en tu ejemplo tienes 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, verá por qué los paquetes contienen módulos y no al revés.

Entonces, en su ejemplo, asumiendo que el Paquete 1 y el Paquete 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 namespace paquete con dos módulos module1 y module2 . y, a menos que tenga una buena razón, probablemente debería colocar los módulos en la carpeta y tener solo eso en la ruta de acceso de python, como se muestra a continuación:

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