Python: importar un subpaquete o submódulo

Resuelto Hibou57 asked hace 12 años • 3 respuestas

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

Diseño del directorio

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

Contenido de init .py

Ambos package/__init__.pyy package/subpackage/__init__.pyestán vacíos.

Contenido demodule.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 masa), 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 hay módulo llamado módulo". Sin embargo …

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

… dice <module 'package.subpackage.module' from '...'>. Entonces eso es un módulo, pero 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

Éste funciona. Entonces, ¿está obligado a usar el prefijo excesivo todo el tiempo o usa la forma insegura como en la versión n.° 1 y Python no le permite usar la forma práctica y segura? ¿La mejor manera, que es segura y evita prefijos largos innecesarios, es la única que Python rechaza? ¿Es esto porque ama import *o porque ama los prefijos demasiado largos (lo que no ayuda a imponer esta práctica)?

Perdón por las palabras duras, pero llevo dos días tratando de solucionar este comportamiento tan estúpido. A menos que esté totalmente equivocado en alguna parte, esto me dejará con la sensación de que algo está realmente roto en el modelo de paquetes y subpaquetes de Python.

Notas

  • No quiero depender de sys.path, para evitar efectos secundarios globales, ni de *.ptharchivos, que son simplemente otra forma de jugar con sys.pathlos mismos efectos globales. Para que la solución sea limpia, tiene que ser únicamente local. Python puede manejar subpaquetes o no, pero no debería requerir jugar con la configuración global para poder manejar cosas locales.
  • También intenté usar imports en package/subpackage/__init__.py, pero no resolvió nada, hace lo mismo y se queja de subpackageque no es un módulo conocido, mientras print subpackagedice que es un módulo (comportamiento extraño, nuevamente).

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

¿Alguna otra forma conocida además de las tres que probé? ¿Algo que no sé?

(suspiro)

----- %< ----- editar ----- >% -----

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 van únicamente a un diccionario global, lo que significa que no hay un diccionario local, lo que implica que no hay forma de administrar la referencia de paquetes local.

Debe utilizar un prefijo completo, un prefijo corto o un alias. Como en:

Versión de prefijo completo

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, puede:

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

No es mi gusto favorito (prefiero tener una declaración de importación por entidad importada), pero puede ser el que prefiera personalmente.

Actualización (14 de septiembre de 2012):

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.
Hibou57 avatar Sep 01 '12 23:09 Hibou57
Aceptado

Parece que no entiendes cómo importse buscan módulos. Cuando utiliza una declaración de importación, siempre busca la ruta real del módulo (y/o sys.modules); no utiliza objetos de módulo en el espacio de nombres local que existen debido a importaciones anteriores. Cuando tu lo hagas:

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

La segunda línea busca un paquete llamado package.subpackagee importa moduledesde ese paquete. Esta línea no tiene ningún efecto sobre la tercera línea. La tercera línea simplemente busca un módulo llamado moduley no encuentra ninguno. No "reutiliza" el objeto llamado moduleque obtuviste en la línea anterior.

En otras palabras, from someModule import ...no significa "del módulo llamado someModule que importé antes..." significa "del módulo llamado someModule que encuentras en sys.path...". No hay forma de crear "incrementalmente" la ruta de un módulo importando los paquetes que conducen a él. Siempre debe hacer referencia al nombre completo del módulo al importar.

No está claro qué estás tratando de lograr. Si solo desea importar el atributo1 del objeto en particular, simplemente hágalo from package.subpackage.module import attribute1y listo. Nunca tendrás que preocuparte por el tiempo package.subpackage.moduleuna vez que hayas importado el nombre que deseas.

Si desea tener acceso al módulo para acceder a otros nombres más adelante, entonces puede hacerlo from package.subpackage import moduley, como ha visto, puede hacerlo module.attribute1y así sucesivamente tanto como desee.

Si desea ambos , es decir, si desea attribute1acceder directamente y desea moduleacceder, simplemente haga ambas 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.subpackageni siquiera dos veces, puedes crear manualmente una referencia local al atributo1:

from package.subpackage import module
attribute1 = module.attribute1
attribute1 # works
module.someOtherAttribute #also works
BrenBarn avatar Sep 01 '2012 17:09 BrenBarn

La razón por la que falla el número 2 es porque sys.modules['module']no existe (la rutina de importación tiene su propio alcance y no puede ver el modulenombre local) y no hay ningún modulemódulo o paquete en el disco. Tenga en cuenta que puede separar varios nombres importados mediante comas.

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

También:

from package.subpackage import module
print module.attribute1
Ignacio Vazquez-Abrams avatar Sep 01 '2012 16:09 Ignacio Vazquez-Abrams