Cómo arreglar "Intento de importación relativa en un paquete" incluso con __init__.py

744

Estoy tratando de seguir PEP 328 , con la siguiente estructura de directorios:

pkg/
  __init__.py
  components/
    core.py
    __init__.py
  tests/
    core_test.py
    __init__.py

En core_test.pytengo la siguiente declaración de importación

from ..components.core import GameLoopEvents

Sin embargo, cuando ejecuto, aparece el siguiente error:

tests$ python core_test.py 
Traceback (most recent call last):
  File "core_test.py", line 3, in <module>
    from ..components.core import GameLoopEvents
ValueError: Attempted relative import in non-package

Al buscar, encontré que "la ruta relativa no funcionaba incluso con __init__.py " e " Importar un módulo desde una ruta relativa ", pero no me ayudaron.

¿Hay algo que me falta aquí?

Skytreader
fuente
17
También estaba muy confundido por las diversas formas de estructurar unittestproyectos, así que escribí este proyecto de muestra bastante exhaustivo que cubre la anidación profunda de módulos, importaciones relativas y absolutas (donde funcionan y no), y referencias relativas y absolutas desde dentro de un paquete, así como importación de clases simple, doble y a nivel de paquete. Ayudado a aclarar las cosas hasta para mí!
cod3monk3y
1
No pude hacer que tus pruebas funcionen. Sigue obteniendo no module named myimports.foocuando los ejecuto.
Blairg23
@ Blairg23 Supongo que la invocación prevista es cdentrar PyImportsy ejecutar python -m unittest tests.test_abs, por ejemplo.
duozmo
77
Estoy de acuerdo con Gene. Desearía que hubiera un mecanismo para depurar el proceso de importación que fuera un poco más útil. En mi caso, tengo dos archivos en el mismo directorio. Estoy tratando de importar un archivo en el otro archivo. Si tengo un archivo init .py en ese directorio, obtengo un ValueError: intento de importación relativa en un error que no es el paquete. Si elimino el archivo init .py, recibo un mensaje de error de ningún módulo llamado 'NOMBRE'.
user1928764
En mi caso, tengo dos archivos en el mismo directorio. Estoy tratando de importar un archivo en el otro archivo. Si tengo un archivo init .py en ese directorio, obtengo un ValueError: intento de importación relativa en un error que no es el paquete. Si elimino el archivo init .py, recibo un mensaje de error de ningún módulo llamado 'NOMBRE'. Lo que es realmente frustrante es que tuve esto funcionando, y luego me pegué un tiro en el pie al eliminar el archivo .bashrc, que configuró el PYTHONPATH en algo, y ahora no funciona.
user1928764

Respuestas:

443

Si. No lo estás usando como un paquete.

python -m pkg.tests.core_test
Ignacio Vazquez-Abrams
fuente
51
Un error: ¡Tenga en cuenta que no hay '.py' al final!
mindthief
497
No soy ninguno de los votantes negativos, pero creo que esto podría usar un poco más de detalle, dada la popularidad de esta pregunta y respuesta. Observando cosas como desde qué directorio ejecutar el comando de shell anterior, el hecho de que necesita __init__.pytodo el camino hacia abajo y el __package__truco de modificación (descrito a continuación por BrenBarn) necesario para permitir estas importaciones de scripts ejecutables (por ejemplo, cuando se usa un shebang y hacer ./my_script.pyen el shell de Unix) sería útil. Todo este problema fue bastante complicado para mí descubrir o encontrar documentación concisa y comprensible.
Mark Amery el
16
Nota: debe estar fuera del directorio pkgen el punto donde llama a esta línea desde la CLI. Entonces, debería funcionar como se esperaba. Si estás dentro pkgy llamas python -m tests.core_test, no funcionará. Al menos no fue para mí.
Blairg23
94
En serio, ¿puedes explicar qué sucede en tu respuesta?
Pinocho
18
@MarkAmery Casi perdí la cabeza tratando de entender cómo funciona todo esto, importaciones relativas dentro de un proyecto con subdirectorios con archivos py que tienen __init__.pyarchivos, pero sigues recibiendo el ValueError: Attempted relative import in non-packageerror. Pagaría un dinero realmente bueno para que alguien, en algún lugar, finalmente explicara en inglés simple cómo funciona todo esto.
AdjunctProfessorFalcon
635

Para elaborar sobre la respuesta de Ignacio Vázquez-Abrams :

El mecanismo de importación de Python funciona en relación con el __name__del archivo actual. Cuando ejecuta un archivo directamente, no tiene su nombre habitual, sino que tiene "__main__"como su nombre. Entonces las importaciones relativas no funcionan.

Puede, como sugirió Igancio, ejecutarlo usando la -mopción Si tiene una parte de su paquete que debe ejecutarse como un script, también puede usar el __package__atributo para decirle a ese archivo qué nombre se supone que debe tener en la jerarquía del paquete.

Ver http://www.python.org/dev/peps/pep-0366/ para más detalles.

BrenBarn
fuente
55
Me llevó un tiempo darme cuenta de que no se puede ejecutar python -m core_testdesde el testssubdirectorio; tiene que ser desde el padre o debe agregar el padre a la ruta.
Aram Kocharyan
3
@DannyStaple: No exactamente. Puede usar __package__para garantizar que los archivos de script ejecutables puedan importar relativamente otros módulos desde el mismo paquete. No hay forma de importar relativamente desde "todo el sistema". Ni siquiera estoy seguro de por qué querrías hacer esto.
BrenBarn
2
Es decir, si el __package__símbolo está configurado como "parent.child", entonces podrá importar "parent.other_child". Quizás no lo dije tan bien.
Danny Staple
55
@DannyStaple: Bueno, cómo funciona se describe en la documentación vinculada. Si usted tiene un guión script.pyen el paquete pack.subpack, a continuación, estableciendo que es __package__a pack.subpackle permitirá hacerlo from ..module import somethinga importar algo de pack.module. Tenga en cuenta que, como dice la documentación, aún debe tener el paquete de nivel superior en la ruta del sistema. Así es como funcionan las cosas para los módulos importados. Lo único que __package__hace es permitirle usar ese comportamiento también para scripts ejecutados directamente.
BrenBarn
3
Lo uso __package__en la secuencia de comandos que se ejecuta directamente, pero desafortunadamente, aparece el siguiente error: "El módulo principal 'xxx' no está cargado, no se puede realizar una importación relativa"
mononoke
202

Puede usarlo import components.coredirectamente si agrega el directorio actual a sys.path:

if __name__ == '__main__' and __package__ is None:
    from os import sys, path
    sys.path.append(path.dirname(path.dirname(path.abspath(__file__))))
ihm
fuente
35
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))esto también funcionará
ajay
26
from os import sysparece trampa :)
ovejas voladoras
3
@Piotr: Se podría considerarse mejor porque es ligeramente muestra más claramente lo que se agrega en sys.path- el padre del directorio del archivo actual se encuentra.
martineau
8
@flyingsheep: De acuerdo, solo usaría un regular import sys, os.path as path.
Martineau
10
Para su información, para usar esto en un cuaderno ipython, me he adaptado a esta respuesta: import os; os.sys.path.append(os.path.dirname(os.path.abspath('.'))). Entonces, una recta import components.corefunciona para mí, importando desde el directorio principal del cuaderno como lo desee.
Racing Tadpole
195

Depende de cómo quieras iniciar tu script.

Si desea iniciar su UnitTest desde la línea de comandos de una manera clásica, es decir:

python tests/core_test.py

Entonces, dado que en este caso 'componentes' y 'pruebas' son carpetas de hermanos, puede importar el módulo relativo utilizando el método insert o append del módulo sys.path . Algo como:

import sys
from os import path
sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
from components.core import GameLoopEvents

De lo contrario, puede iniciar su script con el argumento '-m' (tenga en cuenta que en este caso, estamos hablando de un paquete, y por lo tanto no debe dar la extensión '.py' ), es decir:

python -m pkg.tests.core_test

En tal caso, simplemente puede usar la importación relativa como lo estaba haciendo:

from ..components.core import GameLoopEvents

Finalmente puede mezclar los dos enfoques, para que su script funcione sin importar cómo se llame. Por ejemplo:

if __name__ == '__main__':
    if __package__ is None:
        import sys
        from os import path
        sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
        from components.core import GameLoopEvents
    else:
        from ..components.core import GameLoopEvents
Paolo Rovelli
fuente
3
¿Qué debo hacer si intento usar el pdb para depurar? ya que usas python -m pdb myscript.pypara iniciar la sesión de depuración.
danny
1
@dannynjust - Esa es una buena pregunta ya que no puedes tener 2 módulos principales. En general, al depurar, prefiero colocarme en el depurador manualmente en el primer punto donde quiero comenzar a depurar. Puede hacerlo insertando un import pdb; pdb.set_trace()en el código (en línea).
mgilson
3
¿Es mejor usar en insertlugar de append? Es decir,sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
SparkAndShine
2
El uso de inserción es una mejor coincidencia para la semántica de importación relativa, donde los nombres de paquetes locales tienen prioridad sobre los paquetes instalados. Especialmente para las pruebas, generalmente desea probar la versión local, no la instalada (a menos que su infraestructura de prueba instale el código bajo prueba, en cuyo caso las importaciones relativas son innecesarias y no tendrá este problema).
Alex Dupuy
1
También debe mencionar que no puede estar en el directorio que contiene core_test cuando se ejecuta como un módulo (eso sería demasiado fácil)
Joseph Garvin
25

En core_test.py, haga lo siguiente:

import sys
sys.path.append('../components')
from core import GameLoopEvents
Allan Mwesigwa
fuente
10

Si su caso de uso es para ejecutar pruebas, y parece que es así, puede hacer lo siguiente. En lugar de ejecutar su script de prueba, python core_test.pyuse un marco de prueba como pytest. Luego en la línea de comando puedes ingresar

$$ py.test

Eso ejecutará las pruebas en su directorio. Esto evita el problema de __name__ser __main__que fue señalado por @BrenBarn. Luego, coloque un __init__.pyarchivo vacío en su directorio de prueba, esto hará que el directorio de prueba forme parte de su paquete. Entonces podrás hacer

from ..components.core import GameLoopEvents

Sin embargo, si ejecuta su script de prueba como programa principal, las cosas volverán a fallar. Así que solo usa el corredor de prueba. Tal vez esto también funcione con otros corredores de prueba como, nosetestspero no lo he comprobado. Espero que esto ayude.

deepak
fuente
9

Mi solución rápida es agregar el directorio a la ruta:

import sys
sys.path.insert(0, '../components/')
v4gil
fuente
66
Su enfoque no funcionará en todos los casos porque la parte '../' se resuelve desde el directorio desde el que ejecuta su script (core_test.py). Con su enfoque, se ve obligado a cd a 'pruebas' antes de ejecutar el scritp core_test.py.
xyman
7

El problema es con su método de prueba,

intentaste python core_test.py

entonces obtendrá este error ValueError: intento de importación relativa en un paquete no

Motivo: está probando su embalaje desde una fuente que no es el paquete.

prueba tu módulo desde la fuente del paquete.

si esta es la estructura de tu proyecto,

pkg/
  __init__.py
  components/
    core.py
    __init__.py
  tests/
    core_test.py
    __init__.py

paquete de cd

python -m tests.core_test # dont use .py

o desde fuera del paquete /

python -m pkg.tests.core_test

solo .si desea importar desde la carpeta en el mismo directorio. para cada paso atrás agregue uno más.

hi/
  hello.py
how.py

en how.py

from .hi import hello

en caso de que quieras importar cómo desde hello.py

from .. import how
Mohideen bin Mohammed
fuente
1
Respuesta mejor que aceptada
GabrielBB
En el ejemplo from .. import how, ¿cómo se importa una clase / método específico del archivo 'cómo'? cuando hago el equivalente de from ..how import fooentonces obtengo "intento de importación relativa más allá del paquete de nivel superior"
James Hulse
3

Hilo viejo. Descubrí que la adición de una __all__= ['submodule', ...]a la __init__.py archivo y luego usar el from <CURRENT_MODULE> import *en el objetivo de obras excelentes.

Laurent
fuente
3

Puede usar from pkg.components.core import GameLoopEvents, por ejemplo, uso pycharm, la siguiente es la imagen de la estructura de mi proyecto, solo importo desde el paquete raíz, luego funciona:

ingrese la descripción de la imagen aquí

Jayhello
fuente
3
Esto no funcionó para mí. ¿Tuvo que establecer la ruta en su configuración?
Mohammad Mahjoub
3

Como dijo Paolo , tenemos 2 métodos de invocación:

1) python -m tests.core_test
2) python tests/core_test.py

Una diferencia entre ellos es la cadena sys.path [0]. Dado que la interpretación buscará sys.path al importar , podemos hacer lo siguiente tests/core_test.py:

if __name__ == '__main__':
    import sys
    from pathlib import Path
    sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
    from components import core
    <other stuff>

Y más después de esto, podemos ejecutar core_test.py con otros métodos:

cd tests
python core_test.py
python -m core_test
...

Nota, py36 probado solo.

zhengcao
fuente
3

Este enfoque funcionó para mí y está menos abarrotado que algunas soluciones:

try:
  from ..components.core import GameLoopEvents
except ValueError:
  from components.core import GameLoopEvents

El directorio padre está en mi PYTHONPATH, y hay __init__.pyarchivos en el directorio padre y este directorio.

Lo anterior siempre funcionó en Python 2, pero Python 3 a veces golpeó un ImportError o ModuleNotFoundError (este último es nuevo en Python 3.6 y una subclase de ImportError), por lo que el siguiente ajuste funciona para mí en Python 2 y 3:

try:
  from ..components.core import GameLoopEvents
except ( ValueError, ImportError):
  from components.core import GameLoopEvents
Rick Graves
fuente
2

Prueba esto

import components
from components import *
Vaishnavi Bala
fuente
1

Si alguien está buscando una solución, me topé con una. Aquí hay un poco de contexto. Quería probar uno de los métodos que tengo en un archivo. Cuando lo ejecuto desde dentro

if __name__ == "__main__":

siempre se quejaba de las importaciones relativas. Traté de aplicar las soluciones anteriores, pero no funcionó, ya que había muchos archivos anidados, cada uno con múltiples importaciones.

Esto es lo que hice. Acabo de crear un lanzador, un programa externo que importaría los métodos necesarios y los llamaría. Sin embargo, no es una gran solución, funciona.

HappyWaters
fuente
0

Aquí hay una manera que molestará a todos, pero funcionará bastante bien. En pruebas ejecutadas:

ln -s ../components components

Luego solo importe componentes como lo haría normalmente.

SteveCalifornia
fuente
0

Esto es muy confuso, y si está utilizando IDE como pycharm, es un poco más confuso. Lo que funcionó para mí: 1. Realice la configuración del proyecto pycharm (si está ejecutando python desde un VE o desde el directorio de python) 2. No hay ningún error en la forma en que lo definió. a veces funciona con la clase de importación folder1.file1

si no funciona, use import folder1.file1 3. Su variable de entorno debe mencionarse correctamente en el sistema o proporcionarla en su argumento de línea de comando.

SANTI SANTOSH MAHAPATRA
fuente
-2

Debido a que su código contiene if __name__ == "__main__", que no se importa como un paquete, será mejor que lo use sys.path.append()para resolver el problema.

Rosefun
fuente
No creo que tener if __name__ == "__main__"en su archivo haga una diferencia en nada relacionado con la importación.
user48956