Python: importación de un sub-paquete o sub-módulo


Habiendo ya usado paquetes planos, no esperaba el problema que encontré con los paquetes anidados. Aquí está...

Disposición del directorio

dir
 |
 +-- test.py
 |
 +-- package
      |
      +-- __init__.py
      |
      +-- subpackage
           |
           +-- __init__.py
           |
           +-- module.py

Contenido de init.py

Tanto package/__init__.py como package/subpackage/__init__.py están vacíos.

Contenido de module.py

# file `package/subpackage/module.py`
attribute1 = "value 1"
attribute2 = "value 2"
attribute3 = "value 3"
# and as many more as you want...

Contenido de test.py (3 versiones)

Versión 1

# file test.py
from package.subpackage.module import *
print attribute1 # OK

Esa es la forma mala e insegura de importar cosas (importar todo en un bulto), pero funciona.

Versión 2

# file test.py
import package.subpackage.module
from package.subpackage import module # Alternative
from module import attribute1

Una forma más segura de importar, elemento por elemento, pero falla, Python no quiere esto: falla con el mensaje: "No module named module". Sin embargo ...

# file test.py
import package.subpackage.module
from package.subpackage import module # Alternative
print module # Surprise here

Says dice <module 'package.subpackage.module' from '...'>. Así que eso es un módulo, pero eso no es un módulo / - P 8-O ... uh

Versión 3

# file test.py v3
from package.subpackage.module import attribute1
print attribute1 # OK

Este funciona. Entonces, ¿estás obligado a usar el prefijo overkill todo el tiempo o usar la forma insegura como en la versión #1 y no permitido por Python para usar la forma segura y práctica? La mejor manera, que es seguro y evitar prefijo largo innecesario es el único que Python rechazar? ¿Es esto porque ama import * o porque ama los prefijos superpuestos (lo que no ayuda a hacer cumplir esta práctica)?.

Lo siento por las palabras duras, pero eso es dos días tratando de trabajar alrededor de este comportamiento estúpido. A menos que estuviera totalmente equivocado en algún lugar, esto me dejará con la sensación de que algo está realmente roto en el modelo de paquete y subpaquetes de Python.

Notas

  • no quiero confiar en sys.path, para evitar efectos secundarios globales, ni en archivos *.pth, que son solo otra forma de jugar con sys.path con los mismos efectos globales. Para que la solución sea limpia, tiene que ser solo local. O bien Python es capaz de manejar subpaquetes, o bien no lo es, pero no debería requerir jugar con la configuración global para poder manejar cosas locales.
  • También intenté usar importaciones en package/subpackage/__init__.py, pero no resolvió nada, hizo lo mismo, y se queja de que subpackage no es un módulo conocido, mientras que print subpackage dice que es un módulo (comportamiento extraño, otra vez).

Puede ser que esté completamente equivocado (la opción que preferiría), pero esto me hace sentir muy decepcionado con Python.

¿Alguna otra forma conocida aparte de las tres que probé? ¿Algo que no sepa?

(suspiro)

----- %% -----

Conclusión hasta ahora (después de los comentarios de la gente)

No hay nada como un subpaquete real en Python, ya que todas las referencias de paquetes a un diccionario global, solo, lo que significa que no hay diccionario local, lo que implica que no hay forma de administrar la referencia de paquetes locales.

Debe usar el prefijo completo o el prefijo corto o alias. Como en:

Versión completa del prefijo

from package.subpackage.module import attribute1
# An repeat it again an again
# But after that, you can simply:
use_of (attribute1)

Versión de prefijo corto (pero prefijo repetido)

from package.subpackage import module
# Short but then you have to do:
use_of (module.attribute1)
# and repeat the prefix at every use place

O bien, una variación de lo anterior.

from package.subpackage import module as m
use_of (m.attribute1)
# `m` is a shorter prefix, but you could as well
# define a more meaningful name after the context

Versión factorizada

Si no le importa importar varias entidades a la vez en un lote, usted puede:

from package.subpackage.module import attribute1, attribute2
# and etc.

No en mi primer gusto favorito (prefiero tener una declaración de importación por entidad importada), pero puede ser la que personalmente favorezca.

Actualizar (2012-09-14):

Finalmente parece estar bien en la práctica, excepto con un comentario sobre el diseño. En lugar de lo anterior, usé:

from package.subpackage.module import (

    attribute1, 
    attribute2,
    attribute3,
    ...)  # and etc.
Author: Cœur, 2012-09-01

3 answers

Parece estar malinterpretando cómo import busca módulos. Cuando usa una instrucción import, siempre busca la ruta de acceso del módulo real (y/o sys.modules); no hace uso de los objetos module en el espacio de nombres local que existen debido a importaciones anteriores. Cuando lo hagas:

import package.subpackage.module
from package.subpackage import module
from module import attribute1

La segunda línea busca un paquete llamado package.subpackage e importa module de ese paquete. Esta línea no tiene efecto en la tercera línea. La tercera línea solo busca un módulo llamado module y no encuentra uno. No "reutiliza" el objeto llamado module que obtuviste de la línea anterior.

En otras palabras from someModule import ... no significa "del módulo llamado SomeModule que importé anteriormente..."significa" del módulo llamado SomeModule que se encuentra en sys.camino...". No hay forma de construir "de forma incremental" la ruta de un módulo importando los paquetes que conducen a él. Siempre tiene que hacer referencia al nombre completo del módulo al importar.

No está claro lo que estás tratando de lograr. Si solo desea importar el objeto attribute1 en particular, simplemente haga from package.subpackage.module import attribute1 y termine con él. Nunca tendrá que preocuparse por el largo package.subpackage.module una vez que haya importado el nombre que desea de él.

Si quieres tener acceso al módulo para acceder a otros nombres más tarde, entonces puedes hacer from package.subpackage import module y, como has visto, puedes hacer module.attribute1 y así sucesivamente tanto como quieras.

Si quieres ambos - - - es decir, si quieres attribute1 directamente accesible y desea module accesible, simplemente haga las dos cosas anteriores:

from package.subpackage import module
from package.subpackage.module import attribute1
attribute1 # works
module.someOtherAttribute # also works

Si no te gusta escribir package.subpackage ni siquiera dos veces, puedes crear manualmente una referencia local a attribute1:

from package.subpackage import module
attribute1 = module.attribute1
attribute1 # works
module.someOtherAttribute #also works
 48
Author: BrenBarn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-01 18:59:56

La razón por la que #2 falla es porque sys.modules['module'] no existe (la rutina de importación tiene su propio ámbito, y no puede ver el nombre local module), y no hay un módulo o paquete module en el disco. Tenga en cuenta que puede separar varios nombres importados por comas.

from package.subpackage.module import attribute1, attribute2, attribute3

También:

from package.subpackage import module
print module.attribute1
 7
Author: Ignacio Vazquez-Abrams,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-01 16:50:33

Si todo lo que está tratando de hacer es obtener attribute1 en su espacio de nombres global, la versión 3 parece estar bien. ¿Por qué es el prefijo overkill ?

En la versión 2, en lugar de

from module import attribute1

Puedes hacer

attribute1 = module.attribute1
 0
Author: Thomas Vander Stichele,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-09-01 16:54:27