¿Alguien puede explicar las importaciones relativas de Python?

174

Por mi vida no puedo conseguir que las importaciones relativas de Python funcionen. He creado un ejemplo simple de dónde no funciona:

La estructura del directorio es:

/__init__.py
/start.py
/parent.py
/sub/__init__.py
/sub/relative.py

/start.py contiene solo: import sub.relative

/sub/relative.py contiene solo from .. import parent

Todos los demás archivos están en blanco.

Al ejecutar lo siguiente en la línea de comando:

$ cd /
$ python start.py

Yo obtengo:

Traceback (most recent call last):
  File "start.py", line 1, in <module>
    import sub.relative
  File "/home/cvondrick/sandbox/sub/relative.py", line 1, in <module>
    from .. import parent
ValueError: Attempted relative import beyond toplevel package

Estoy usando Python 2.6. ¿Por qué es este el caso? ¿Cómo hago que este ejemplo de sandbox funcione?

carl
fuente

Respuestas:

140

Está importando desde el paquete "sub". start.pyno está en sí mismo en un paquete incluso si hay un __init__.pyregalo.

Debería iniciar su programa desde un directorio sobre parent.py:

./start.py

./pkg/__init__.py
./pkg/parent.py
./pkg/sub/__init__.py
./pkg/sub/relative.py

Con start.py:

import pkg.sub.relative

Ahora pkg es el paquete de nivel superior y su importación relativa debería funcionar.


Si desea seguir con su diseño actual, puede usarlo import parent. Debido a que utiliza start.pypara iniciar su intérprete, el directorio donde start.pyse encuentra está en su ruta de Python. parent.pyvive allí como un módulo separado.

También puede eliminar de forma segura el nivel superior __init__.py, si no importa nada en un script más arriba del árbol de directorios.

ebo
fuente
2
Está confundiendo los términos 'módulo' y 'paquete'. 'start.py' representa el módulo 'start', 'mod' y 'mod.sub' son paquetes, 'mod' es un paquete de nivel superior.
Ferdinand Beyer
34
Gracias, pero esto honestamente parece realmente tonto. Para un lenguaje tan hermoso, no puedo creer que los diseñadores creen una restricción así. ¿No hay otra forma?
carl
2
No es tonto en absoluto. Las importaciones relativas son un medio para referirse a los módulos hermanos dentro de un paquete. Si desea importar un módulo de nivel superior, use importaciones absolutas.
Ferdinand Beyer
58
¿No es tonto? Entonces, en bash, ¿no pudo abordar el directorio superior relativo con ".." no le molestaría?
e-satis
2
Me parece que la idea de Python es usar importaciones "absolutas" del directorio donde lanzó su script principal. Por lo tanto, puede usar la ruta absoluta "import parent" para importar el módulo padre desde el hermano. Y el pariente importa algún tipo de legado o lo que sea ...
Odiseo
35

Si va a llamar relative.pydirectamente, es decir, si realmente desea importar desde un módulo de nivel superior, debe agregarlo explícitamente a la sys.pathlista.
Así es como debería funcionar:

# Add this line to the beginning of relative.py file
import sys
sys.path.append('..')

# Now you can do imports from one directory top cause it is in the sys.path
import parent

# And even like this:
from parent import Parent

Si cree que lo anterior puede causar algún tipo de inconsistencia, puede usar esto en su lugar:

sys.path.append(sys.path[0] + "/..")

sys.path[0] se refiere a la ruta desde la que se ejecutó el punto de entrada.

AmirHossein
fuente
3

Comprobándolo en python3:

python -V
Python 3.6.5

Ejemplo 1:

.
├── parent.py
├── start.py
└── sub
    └── relative.py

- start.py
import sub.relative

- parent.py
print('Hello from parent.py')

- sub/relative.py
from .. import parent

Si lo ejecutamos así (solo para asegurarnos de que PYTHONPATH esté vacío):

PYTHONPATH='' python3 start.py

Salida:

Traceback (most recent call last):
  File "start.py", line 1, in <module>
    import sub.relative
  File "/python-import-examples/so-example-v1/sub/relative.py", line 1, in <module>
    from .. import parent
ValueError: attempted relative import beyond top-level package

Si cambiamos la importación en sub/relative.py

- sub/relative.py
import parent

Si lo ejecutamos así:

PYTHONPATH='' python3 start.py

Salida:

Hello from parent.py

Ejemplo2:

.
├── parent.py
└── sub
    ├── relative.py
    └── start.py

- parent.py
print('Hello from parent.py')

- sub/relative.py
print('Hello from relative.py')

- sub/start.py
import relative
from .. import parent

Ejecútalo como:

PYTHONPATH='' python3 sub/start.py

Salida:

Hello from relative.py
Traceback (most recent call last):
  File "sub/start.py", line 2, in <module>
    from .. import parent
ValueError: attempted relative import beyond top-level package

Si cambiamos la importación en sub/start.py:

- sub/start.py
import relative
import parent

Ejecútalo como:

PYTHONPATH='' python3 sub/start.py

Salida:

Hello from relative.py
Traceback (most recent call last):
  File "sub/start.py", line 3, in <module>
    import parent
ModuleNotFoundError: No module named 'parent'

Ejecútalo como:

PYTHONPATH='.' python3 sub/start.py

Salida:

Hello from relative.py
Hello from parent.py

También es mejor usar la importación desde la carpeta raíz, es decir:

- sub/start.py
import sub.relative
import parent

Ejecútalo como:

PYTHONPATH='.' python3 sub/start.py

Salida:

Hello from relative.py
Hello from parent.py
mrgloom
fuente