python - non - Importaciones relativas por millonésima vez.
python import module from path (8)
Las importaciones relativas utilizan el atributo de nombre de un módulo para determinar la posición de ese módulo en la jerarquía de paquetes. Si el nombre del módulo no contiene ninguna información de paquete (por ejemplo, está configurado como ''principal''), las importaciones relativas se resuelven como si el módulo fuera un módulo de nivel superior, independientemente de dónde se encuentre el módulo en el sistema de archivos.
Escribió un pequeño paquete de Python a PyPi que podría ayudar a los espectadores de esta pregunta. El paquete actúa como solución alternativa si uno desea poder ejecutar archivos de Python que contienen importaciones que contienen paquetes de nivel superior desde un paquete / proyecto sin estar directamente en el directorio del archivo de importación. https://pypi.org/project/import-anywhere/
Yo he estado aquí:
- http://www.python.org/dev/peps/pep-0328/
- http://docs.python.org/2/tutorial/modules.html#packages
- Paquetes de Python: importaciones relativas
- El código de ejemplo de importación relativa de Python no funciona
- Última respuesta a las importaciones relativas de python
- Importaciones relativas en Python
- Python: Deshabilitando la importación relativa
y muchas URL que no copié, algunas en SO, otras en otros sitios, cuando pensé que tendría la solución rápidamente.
La pregunta que siempre se repite es la siguiente: con Windows 7, Python 2.7.3 de 32 bits, ¿cómo resuelvo este mensaje "Intento de importación relativa en un paquete que no es un paquete"? Construí una réplica exacta del paquete en pep-0328:
package/
__init__.py
subpackage1/
__init__.py
moduleX.py
moduleY.py
subpackage2/
__init__.py
moduleZ.py
moduleA.py
Hice funciones llamadas spam y huevos en sus módulos apropiados. Naturalmente, no funcionó. La respuesta está aparentemente en la 4ta URL que he enumerado, pero para mí todos son antiguos alumnos. Hubo esta respuesta en una de las URL que visité:
Las importaciones relativas utilizan el atributo de nombre de un módulo para determinar la posición de ese módulo en la jerarquía de paquetes. Si el nombre del módulo no contiene ninguna información de paquete (por ejemplo, está configurado como ''principal''), las importaciones relativas se resuelven como si el módulo fuera un módulo de nivel superior, independientemente de dónde se encuentre el módulo en el sistema de archivos.
La respuesta anterior parece prometedora, pero para mí todos son jeroglíficos. Entonces, mi pregunta, ¿cómo puedo hacer que Python no me devuelva "Intento de importación relativa en un no paquete"? Tiene una respuesta que implica -m, supuestamente.
¿Puede alguien decirme por qué Python da ese mensaje de error, lo que significa no empaquetado? , por qué y cómo se define un "paquete", y la respuesta precisa se expresa de manera tan fácil como para que un estudiante de kindergarten pueda entender .
Edición: Las importaciones se realizaron desde la consola.
Aquí hay una receta general, modificada como ejemplo, que estoy usando ahora para tratar con las bibliotecas de Python escritas como paquetes, que contienen archivos interdependientes, donde quiero poder probar partes de ellos por partes. Llamemos a este lib.foo
y digamos que necesita acceso a lib.fileA
para las funciones f1
y f2
, y lib.fileB
para la clase Class3
.
He incluido algunas llamadas print
para ayudar a ilustrar cómo funciona esto. En la práctica, querrá eliminarlos (y tal vez también from __future__ import print_function
).
Este ejemplo en particular es demasiado simple para mostrar cuando realmente necesitamos insertar una entrada en sys.path
. (Consulte la respuesta de Lars para un caso en el que la necesitamos, cuando tenemos dos o más niveles de directorios de paquetes, y luego usamos os.path.dirname(os.path.dirname(__file__))
—pero no es así realmente me duele aquí.) También es lo suficientemente seguro como para hacer esto sin la prueba if _i in sys.path
. Sin embargo, si cada archivo importado inserta la misma ruta, por ejemplo, si tanto el fileA
como el fileA
fileB
quieren importar utilidades del paquete, esto sys.path
la ruta sys.path
muchas veces, por lo que es bueno tener la if _i not in sys.path
en la if _i not in sys.path
de if _i not in sys.path
.
from __future__ import print_function # only when showing how this works
if __package__:
print(''Package named {!r}; __name__ is {!r}''.format(__package__, __name__))
from .fileA import f1, f2
from .fileB import Class3
else:
print(''Not a package; __name__ is {!r}''.format(__name__))
# these next steps should be used only with care and if needed
# (remove the sys.path manipulation for simple cases!)
import os, sys
_i = os.path.dirname(os.path.abspath(__file__))
if _i not in sys.path:
print(''inserting {!r} into sys.path''.format(_i))
sys.path.insert(0, _i)
else:
print(''{!r} is already in sys.path''.format(_i))
del _i # clean up global name space
from fileA import f1, f2
from fileB import Class3
... all the code as usual ...
if __name__ == ''__main__'':
import doctest, sys
ret = doctest.testmod()
sys.exit(0 if ret.failed == 0 else 1)
La idea aquí es esta (y tenga en cuenta que todos estos funcionan de la misma manera en python2.7 y python 3.x):
Si se ejecuta como
import lib
ofrom lib import foo
como una importación de paquete normal desde un código ordinario,__package
eslib
y__name__
eslib.foo
. Tomamos la primera ruta de código, importando desde.fileA
, etc.Si se ejecuta como
python lib/foo.py
,__package__
será None y__name__
será__main__
.Tomamos el segundo camino del código. El directorio
lib
ya estará ensys.path
por lo que no es necesario agregarlo. Importamos desdefileA
, etc.Si se ejecuta dentro del directorio
lib
comopython foo.py
, el comportamiento es el mismo que para el caso 2.Si se ejecuta dentro del directorio
lib
comopython -m foo
, el comportamiento es similar a los casos 2 y 3. Sin embargo, la ruta al directoriolib
no se encuentra ensys.path
, por lo que lo agregamos antes de importar. Lo mismo se aplica si ejecutamos Python y luegoimport foo
.(Ya que
.
Está ensys.path
, no necesitamos agregar la versión absoluta de la ruta aquí. Aquí es donde una estructura de anidamiento de paquetes más profunda, donde queremos hacerlofrom ..otherlib.fileC import ...
, hace una diferencia. Si no está haciendo esto, puede omitir toda la manipulación desys.path
completo.)
Notas
Todavía hay una peculiaridad. Si manejas todo esto desde afuera:
$ python2 lib.foo
o:
$ python3 lib.foo
el comportamiento depende del contenido de lib/__init__.py
. Si eso existe y está vacío , todo está bien:
Package named ''lib''; __name__ is ''__main__''
Pero si lib/__init__.py
sí importa la routine
para que pueda exportar routine.name
directamente como lib.name
, obtendrá:
$ python2 lib.foo
Package named ''lib''; __name__ is ''lib.foo''
Package named ''lib''; __name__ is ''__main__''
Es decir, el módulo se importa dos veces, una vez a través del paquete y luego otra vez como __main__
para que ejecute su código main
. Python 3.6 y posteriores advierten sobre esto:
$ python3 lib.routine
Package named ''lib''; __name__ is ''lib.foo''
[...]/runpy.py:125: RuntimeWarning: ''lib.foo'' found in sys.modules
after import of package ''lib'', but prior to execution of ''lib.foo'';
this may result in unpredictable behaviour
warn(RuntimeWarning(msg))
Package named ''lib''; __name__ is ''__main__''
La advertencia es nueva, pero el comportamiento advertido no lo es. Es parte de lo que algunos llaman la trampa de doble importación . (Para detalles adicionales vea el número 27487 ). Nick Coghlan dice:
Esta siguiente trampa existe en todas las versiones actuales de Python, incluida la 3.3, y se puede resumir en la siguiente guía general: "Nunca agregue un directorio de paquetes, o cualquier directorio dentro de un paquete, directamente a la ruta de Python".
Tenga en cuenta que aunque violamos esa regla aquí, lo hacemos solo cuando el archivo que se está cargando no se está cargando como parte de un paquete, y nuestra modificación está diseñada específicamente para permitirnos acceder a otros archivos en ese paquete. (Y, como señalé, probablemente no deberíamos hacer esto para los paquetes de un solo nivel). Si quisiéramos ser más limpios, podríamos reescribir esto como, por ejemplo:
import os, sys
_i = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if _i not in sys.path:
sys.path.insert(0, _i)
else:
_i = None
from sub.fileA import f1, f2
from sub.fileB import Class3
if _i:
sys.path.remove(_i)
del _i
Es decir, modificamos sys.path
tiempo suficiente para lograr nuestras importaciones, luego lo devolvemos a la forma en que estaba (eliminando una copia de _i
si y solo si agregamos una copia de _i
).
Aquí hay una solución que no recomendaría, pero podría ser útil en algunas situaciones donde los módulos simplemente no se generaron:
import os
import sys
parent_dir_name = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
sys.path.append(parent_dir_name + "/your_dir")
import your_script
your_script.a_function()
Así que después de hablar sobre esto junto con muchos otros, encontré una nota publicada por Dorian B en este article que resolvía el problema específico que tenía donde desarrollar módulos y clases para usar con un servicio web, pero también quiero estar capaz de probarlos mientras estoy codificando, usando las facilidades de depuración en PyCharm. Para ejecutar pruebas en una clase autocontenida, incluiría lo siguiente al final de mi archivo de clase:
if __name__ == ''__main__'':
# run test code here...
pero si quisiera importar otras clases o módulos en la misma carpeta, tendría que cambiar todas mis declaraciones de importación de notación relativa a referencias locales (es decir, eliminar el punto (.)) Pero después de leer la sugerencia de Dorian, intenté su '' one-liner ''y funcionó! ¡Ahora puedo probar en PyCharm y dejar mi código de prueba en su lugar cuando uso la clase en otra clase en prueba, o cuando lo uso en mi servicio web!
# import any site-lib modules first, then...
import sys
parent_module = sys.modules[''.''.join(__name__.split(''.'')[:-1]) or ''__main__'']
if __name__ == ''__main__'' or parent_module.__name__ == ''__main__'':
from codex import Codex # these are in same folder as module under test!
from dblogger import DbLogger
else:
from .codex import Codex
from .dblogger import DbLogger
La instrucción if comprueba si estamos ejecutando este módulo como principal o si se está utilizando en otro módulo que se está probando como principal . Quizás esto sea obvio, pero ofrezco esta nota aquí en caso de que alguien más frustrado por los problemas de importación relativos mencionados arriba pueda hacer uso de ella.
Esto es realmente un problema dentro de python. El origen de la confusión es que la gente toma erróneamente la importación relativa como una ruta relativa que no lo es.
Por ejemplo, cuando escribes en faa.py :
from .. import foo
Esto tiene un significado solo si faa.y fue identificado y cargado por python, durante la ejecución, como parte de un paquete. En ese caso, el nombre del módulo para faa.py sería, por ejemplo, some_packagename.faa . Si el archivo se cargó solo porque está en el directorio actual, cuando se ejecuta python, entonces su nombre no se referirá a ningún paquete y eventualmente la importación relativa fallará.
Una solución simple para referir módulos en el directorio actual, es usar esto:
if __package__ is None or __package__ == '''':
#uses current directory visibility
import foo
else:
#uses current package visibility
from . import foo
Tuve un problema similar en el que no quería cambiar la ruta de búsqueda del módulo Python y necesitaba cargar un módulo relativamente desde una secuencia de comandos (a pesar de que "las secuencias de comandos no se pueden importar en relación con todas", como BrenBarn explicó muy bien).
Así que utilicé el siguiente hack. Desafortunadamente, se basa en el módulo imp
que se ha desaprobado desde la versión 3.4 para ser eliminado en favor de importlib
. (¿Es esto posible con importlib
, también? No lo sé.) Sin embargo, el hack funciona por ahora.
Ejemplo para acceder a miembros de moduleX
en subpackage1
desde un script que reside en la carpeta subpackage2
:
#!/usr/bin/env python3
import inspect
import imp
import os
def get_script_dir(follow_symlinks=True):
"""
Return directory of code defining this very function.
Should work from a module as well as from a script.
"""
script_path = inspect.getabsfile(get_script_dir)
if follow_symlinks:
script_path = os.path.realpath(script_path)
return os.path.dirname(script_path)
# loading the module (hack, relying on deprecated imp-module)
PARENT_PATH = os.path.dirname(get_script_dir())
(x_file, x_path, x_desc) = imp.find_module(''moduleX'', [PARENT_PATH+''/''+''subpackage1''])
module_x = imp.load_module(''subpackage1.moduleX'', x_file, x_path, x_desc)
# importing a function and a value
function = module_x.my_function
VALUE = module_x.MY_CONST
Un enfoque más limpio parece ser modificar el sys.path utilizado para cargar módulos como lo menciona Federico.
#!/usr/bin/env python3
if __name__ == ''__main__'' and __package__ is None:
from os import sys, path
# __file__ should be defined in this case
PARENT_DIR = path.dirname(path.dirname(path.abspath(__file__)))
sys.path.append(PARENT_DIR)
from subpackage1.moduleX import *
__name__
cambia dependiendo de si el código en cuestión se ejecuta en el espacio de nombres global o como parte de un módulo importado.
Si el código no se está ejecutando en el espacio global, __name__
será el nombre del módulo. Si se está ejecutando en el espacio de nombres global, por ejemplo, si lo escribe en una consola, o ejecuta el módulo como una secuencia de comandos utilizando python.exe yourscriptnamehere.py
, __name__
convierte en "__main__"
.
Verá una gran cantidad de código de Python if __name__ == ''__main__''
se usa para probar si el código se está ejecutando desde el espacio de nombres global, lo que le permite tener un módulo que se duplica como un script.
¿Intentaste hacer estas importaciones desde la consola?
Guión vs. Módulo
Aquí hay una explicación. La versión corta es que hay una gran diferencia entre ejecutar directamente un archivo de Python e importar ese archivo desde otro lugar. El solo hecho de saber en qué directorio está el archivo no determina en qué paquete Python cree que está. Eso depende, además, de cómo se carga el archivo en Python (ejecutándolo o importándolo).
Hay dos formas de cargar un archivo Python: como la secuencia de comandos de nivel superior o como un módulo. Un archivo se carga como la secuencia de comandos de nivel superior si lo ejecuta directamente, por ejemplo, escribiendo python myfile.py
en la línea de comandos. Se carga como un módulo si haces python -m myfile
, o si se carga cuando se encuentra una declaración de import
dentro de otro archivo. Solo puede haber un script de nivel superior a la vez; El script de nivel superior es el archivo Python que ejecutó para comenzar.
Nombrar
Cuando se carga un archivo, se le da un nombre (que se almacena en su atributo __name__
). Si se cargó como la secuencia de comandos de nivel superior, su nombre es __main__
. Si se cargó como un módulo, su nombre es el nombre del archivo, precedido por los nombres de los paquetes / subpaquetes de los que forma parte, separados por puntos.
Así, por ejemplo, en su ejemplo:
package/
__init__.py
subpackage1/
__init__.py
moduleX.py
moduleA.py
si importó moduleX
(nota: importado , no ejecutado directamente), su nombre sería package.subpackage1.moduleX
. Si importó el moduleA
, su nombre sería package.moduleA
. Sin embargo, si ejecuta moduleX
directamente desde la línea de comandos, su nombre será __main__
, y si ejecuta moduleA
directamente desde la línea de comandos, su nombre será __main__
. Cuando un módulo se ejecuta como la secuencia de comandos de nivel superior, pierde su nombre normal y su nombre es __main__
.
Accediendo a un módulo NO a través de su paquete que contiene
Existe un problema adicional: el nombre del módulo depende de si se importó "directamente" desde el directorio en el que se encuentra o si se importó a través de un paquete. Esto solo hace una diferencia si ejecuta Python en un directorio e intenta importar un archivo en ese mismo directorio (o un subdirectorio de este). Por ejemplo, si inicia el intérprete de Python en el directorio package/subpackage1
y luego import moduleX
, el nombre de moduleX
solo será moduleX
, y no package.subpackage1.moduleX
. Esto se debe a que Python agrega el directorio actual a su ruta de búsqueda en el inicio; Si encuentra el módulo a importar en el directorio actual, no sabrá que ese directorio es parte de un paquete, y la información del paquete no se convertirá en parte del nombre del módulo.
Un caso especial es si ejecuta el intérprete interactivamente (por ejemplo, simplemente escriba python
y comience a ingresar el código de Python sobre la marcha). En este caso, el nombre de esa sesión interactiva es __main__
.
Ahora, aquí está lo crucial para su mensaje de error: si el nombre de un módulo no tiene puntos, no se considera parte de un paquete . No importa dónde esté el archivo en el disco. Lo único que importa es cuál es su nombre y su nombre depende de cómo lo cargó.
Ahora mire la cita que incluyó en su pregunta:
Las importaciones relativas utilizan el atributo de nombre de un módulo para determinar la posición de ese módulo en la jerarquía de paquetes. Si el nombre del módulo no contiene ninguna información de paquete (por ejemplo, está configurado como ''principal''), las importaciones relativas se resuelven como si el módulo fuera un módulo de nivel superior, independientemente de dónde se encuentre el módulo en el sistema de archivos.
Importaciones relativas ...
Las importaciones relativas utilizan el nombre del módulo para determinar dónde se encuentra en un paquete. Cuando se utiliza una importación relativa como from .. import foo
, los puntos indican que se incrementa cierto número de niveles en la jerarquía de paquetes. Por ejemplo, si el nombre de su módulo actual es package.subpackage1.moduleX
, entonces ..moduleA
significaría package.moduleA
. Para que una from .. import
funcione, el nombre del módulo debe tener al menos tantos puntos como hay en la declaración de import
.
... son solo familiares en un paquete
Sin embargo, si el nombre de su módulo es __main__
, no se considera que esté en un paquete. Su nombre no tiene puntos y, por lo tanto, no puede usar from .. import
sentencias de from .. import
que contiene. Si intenta hacerlo, obtendrá el error "relativo a la importación en un paquete no".
Los scripts no pueden importar parientes
Lo que probablemente hizo es tratar de ejecutar moduleX
o similar desde la línea de comandos. Cuando hizo esto, su nombre se estableció en __main__
, lo que significa que las importaciones relativas en él fallarán, porque su nombre no revela que está en un paquete. Tenga en cuenta que esto también ocurrirá si ejecuta Python desde el mismo directorio donde se encuentra un módulo, y luego intenta importar ese módulo porque, como se describió anteriormente, Python encontrará el módulo en el directorio actual "demasiado pronto" sin darse cuenta de que es parte de un paquete.
Recuerde también que cuando ejecuta el intérprete interactivo, el "nombre" de esa sesión interactiva siempre es __main__
. Por lo tanto, no puede realizar importaciones relativas directamente desde una sesión interactiva . Las importaciones relativas son solo para uso dentro de archivos de módulo.
Dos soluciones:
Si realmente desea ejecutar
moduleX
directamente, pero aún quiere que se considere parte de un paquete, puede hacerpython -m package.subpackage1.moduleX
. La-m
le dice a Python que lo cargue como un módulo, no como el script de nivel superior.O tal vez no desee ejecutar el
moduleX
, solo desea ejecutar otro script, por ejemplo,myfile.py
, que usa funciones dentro delmoduleX
. Si ese es el caso, pongamyfile.py
en otro lugar ( no dentro del directorio delpackage
) y ejecútelo. Si dentro demyfile.py
haces cosas comofrom package.moduleA import spam
, funcionará bien.
Notas
Para cualquiera de estas soluciones, el directorio del paquete (
package
en su ejemplo) debe ser accesible desde la ruta de búsqueda del módulo Python (sys.path
). Si no lo es, no podrá utilizar nada en el paquete de forma confiable.Desde Python 2.6, el "nombre" del módulo para propósitos de resolución de paquetes está determinado no solo por sus atributos
__name__
sino también por el atributo__package__
. Es por eso que evito usar el símbolo explícito__name__
para referirme al "nombre" del módulo. Como Python 2.6, el "nombre" de un módulo es efectivamente__package__ + ''.'' + __name__
__package__ + ''.'' + __name__
, o simplemente__name__
si__package__
esNone
.)