Python: importar un subpaquete o submódulo

90

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

Diseño de 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 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 a granel), 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: "Ningún 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 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, ¿se ve obligado a usar el prefijo overkill 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 el prefijo largo innecesario, ¿es la única que Python rechaza? ¿Es esto porque ama import *o porque ama los prefijos demasiado largos (lo que no ayuda a hacer cumplir esta práctica) ?.

Perdón por las palabras difíciles, pero hace dos días que intento solucionar este comportamiento 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 paquete y subpaquetes de Python.

Notas

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

Puede que me equivoque por completo (la opción que preferiría), pero esto me decepcionó mucho con Python.

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

(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 a un diccionario global, solo, lo que significa que no hay un diccionario local, lo que implica que no hay forma de administrar la referencia del paquete local.

Tienes que usar prefijo completo o prefijo corto o 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 primer gusto favorito (prefiero tener una declaración de importación por entidad importada), pero puede que sea la que prefiera personalmente.

Actualización (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.
Hibou57
fuente
¿Cómo van las cosas cuando escribe "from. Import module" en "/package/subpackage/__init__.py"?
Markus Unterwaditzer
Su "versión factorizada" parece exactamente la adecuada para lo que quiere hacer. Si hace una línea de importación separada para el atributo1 y el atributo2 (como "prefiera"), simplemente se está dando más trabajo deliberadamente. No hay razón para hacer eso.
BrenBarn
Lo siento, pero no consigo lo que quieres. ¿Podría reformular su pregunta de una manera más clara? ¿Qué le gustaría hacer exactamente? Quiero decir, ¿qué le gustaría escribir que no funciona y cómo espera que funcione? Por lo que leí, creo que sabes qué incluye la semántica de la importación como Java o quizás C Lo último: puede hacer que un módulo "importación en estrella" sea seguro agregando una __all__variable que contenga una lista de los nombres que deben exportarse cuando se importen en estrella. editar: Bien, leyendo la respuesta de BrenBarn entendí lo que querías decir.
Bakuriu

Respuestas:

68

Parece que no entiende cómo importbusca módulos. Cuando usa una declaración de importación, siempre busca la ruta del módulo real (y / o sys.modules); no hace uso de 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 solo busca un módulo llamado moduley no encuentra uno. No "reutiliza" el objeto llamado moduleque obtuvo 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.path ...". No hay forma de construir "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á tratando de lograr. Si solo desea importar el atributo1 del objeto en particular, hágalo from package.subpackage.module import attribute1y termine con él. Nunca debe preocuparse por el tiempo package.subpackage.moduleuna vez que haya importado el nombre que desea.

Si no desea tener acceso al módulo de acceso a otros nombres más adelante, entonces puede hacerlo from package.subpackage import moduley, como hemos visto, a continuación, puede hacer module.attribute1y así sucesivamente tanto como te gusta.

Si desea ambos --- es decir, si desea attribute1acceder directamente y desea ser moduleaccesible, 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 le gusta escribir package.subpackageni siquiera dos veces, puede crear manualmente una referencia local al atributo1:

from package.subpackage import module
attribute1 = module.attribute1
attribute1 # works
module.someOtherAttribute #also works
BrenBarn
fuente
Tus comentarios van en la misma dirección que los de Ignacio Vázquez-Abrams (yo he comentado su mensaje). Su adición al final, sobre el uso module.attribute1es algo en lo que pensé, pero pensé que habría una manera de evitar la necesidad de un prefijo en todas partes. Así que tengo que usar un prefijo en todas partes, o crear un alias local, repitiendo el nombre. No es el estilo que esperaba, pero si no hay manera (después de todo, estoy acostumbrado a Ada, que requiere algo similar con sus declaraciones de cambio de nombre).
Hibou57
@ Hibou57: Todavía no me queda claro qué está tratando de lograr en su "Versión 2". ¿Qué quieres hacer que no sea posible? ¿Desea no volver a escribir nunca ninguna parte del nombre del paquete / módulo / atributo, pero aún así importar tanto el módulo como su atributo?
BrenBarn
Quería tener una referencia de paquete local, al igual que la forma en que puede tener una referencia de objeto local. Parece que finalmente hay una referencia de módulo realmente local, pero no puede importar desde estos. Es una mezcla de local y global con un sabor divertido (algunas cosas pueden ser locales, otras deben ser globales, no me gusta, pero estoy bien siempre que entienda mejor cómo funciona). Gracias por tu mensaje por cierto.
Hibou57
1
No estoy seguro de que todavía entiendas cómo funciona. O que hiciste en 2012 en cualquier caso.
Hejazzman
1
Cada vez que vuelvo a Python después de un descanso de 6 meses, termino aquí. ¡Si tan solo pudiera votar cada vez que visito esta página! Voy a hacer un póster gigantesco con esta frase: "No hay forma de construir" incrementalmente "la ruta de un módulo importando los paquetes que conducen a él".
PatrickT
10

La razón # 2 falla 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 con comas.

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

También:

from package.subpackage import module
print module.attribute1
Ignacio Vázquez-Abrams
fuente
Su referencia, sys.modules['name']que no conocía hasta ahora, me hizo pensar que eso era lo que temía (y BrenBarn lo confirma): no hay nada como subpaquetes reales en Python. sys.modules, como sugiere su nombre, es global, y si todas las referencias a módulos se basan en esto, entonces no hay nada como una referencia local a un módulo (¿puede venir con Python 3.x?).
Hibou57
Su uso de "referencia" es ambiguo; el primero importen # 2 genera una referencia local para package.subpackage.moduleenlazar module.
Ignacio Vazquez-Abrams
Sí, pero es un "módulo" desde el que no puedo importar ;-)
Hibou57
0

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

En la versión 2, en lugar de

from module import attribute1

tu puedes hacer

attribute1 = module.attribute1
Thomas Vander Stichele
fuente
attribute1 = module.attribute1es simplemente repetir el nombre sin ningún valor agregado. Sé que funciona, pero no me gusta este estilo (lo que no implica que no me guste tu respuesta).
Hibou57
2
Supongo que yo, como todas las personas que comentan aquí, no entiendo lo que quieres hacer. En todos los ejemplos que da, parece que desea terminar con un símbolo de un subpaquete en su espacio de nombres. Su ejemplo que no funciona (ejemplo 2) quiere hacerlo importando un submódulo de un paquete y luego importando un símbolo de ese submódulo. No sé por qué quiere hacerlo en dos pasos en lugar de uno. Tal vez explique más cuál sería su solución ideal y por qué.
Thomas Vander Stichele