Cómo corregir “Intento de importación relativa en un no paquete” incluso con __init__.py

Estoy tratando de seguir el 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.py tengo la siguiente statement de importación

 from ..components.core import GameLoopEvents 

Sin embargo, cuando ejecuto, me sale el siguiente error:

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

Buscando alrededor encontré “la ruta relativa que no funciona incluso con __init__.py ” e ” Importar un módulo desde una ruta relativa ” pero no ayudaron.

¿Hay algo que me esté perdiendo aquí?

Sí. No lo estás utilizando como un paquete.

 python -m pkg.tests.core_test 

Para profundizar en 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. Así que las importaciones relativas no funcionan.

Puede, como sugirió Igancio, ejecutarlo usando la opción -m . Si tiene una parte de su paquete que debe ejecutarse como un script, también puede usar el atributo __package__ para indicar a ese archivo el nombre que se supone que debe tener en la jerarquía de paquetes.

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

Puede usar import components.core directamente si sys.path 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__)))) 

Depende de cómo quieras lanzar 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 

Luego, dado que en este caso los ‘componentes’ y ‘pruebas’ son carpetas de hermanos, puede importar el módulo relativo utilizando el método de inserción o el método de adición 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 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 

En core_test.py, haga lo siguiente:

 import sys sys.path.append('../components') from core import GameLoopEvents 

Si su caso de uso es para ejecutar pruebas, y parece que sí, puede hacer lo siguiente. En lugar de ejecutar su script de prueba como python core_test.py use 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 __main__ el problema de que __name__ sea __main__ que fue señalado por @BrenBarn. Luego, coloque un archivo __init__.py vacío en su directorio de prueba, esto hará que el directorio de prueba sea parte de su paquete. Entonces podrás hacer

 from ..components.core import GameLoopEvents 

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

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

 import sys sys.path.insert(0, '../components/') 

Hilo viejo Descubrí que agregar un __all__= ['submodule', ...] al archivo __init__.py y luego usar la from import * en el destino funciona bien.

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

introduzca la descripción de la imagen aquí

Si alguien está buscando una solución, me encontré con uno. 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. Intenté aplicar las soluciones anteriores, pero no funcionó, ya que había muchos archivos nesteds, cada uno con múltiples importaciones.

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

Prueba esto

 import components from components import * 

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 sys.path [0] cadena. Como la interpretación buscará sys.path cuando se realice una importación , podemos hacerlo con 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  

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 ... 

Tenga en cuenta, py36 probado solamente.