más allá del error de paquete de nivel superior en la importación relativa

317

Parece que ya hay algunas preguntas sobre la importación relativa en Python 3, pero después de analizar muchas de ellas, todavía no encontré la respuesta a mi problema. Así que aquí está la cuestión.

Tengo un paquete que se muestra a continuación

package/
   __init__.py
   A/
      __init__.py
      foo.py
   test_A/
      __init__.py
      test.py

y tengo una sola línea en test.py:

from ..A import foo

ahora estoy en la carpeta de packagey ejecuto

python -m test_A.test

Recibí un mensaje

"ValueError: attempted relative import beyond top-level package"

pero si estoy en la carpeta principal de package, por ejemplo, ejecuto:

cd ..
python -m package.test_A.test

todo esta bien.

Ahora mi pregunta es: cuando estoy en la carpeta de package, y ejecuto el módulo dentro del subpaquete test_A ya que test_A.test, según mi entendimiento, ..Asube solo un nivel, que todavía está dentro de la packagecarpeta, por qué dice el mensaje beyond top-level package. ¿Cuál es exactamente la razón que causa este mensaje de error?

refugio
fuente
49
esa publicación no explicó mi error "más allá del paquete de nivel superior"
refugio
44
Tengo un pensamiento aquí, así que cuando ejecuto test_A.test como módulo, '..' pasa por encima de test_A, que ya es el nivel más alto de la prueba test_A.test, creo que el nivel del paquete no es el nivel del directorio, sino cuántos niveles que importa el paquete.
refugio
2
Te prometo que entenderás todo sobre la importación relativa después de ver esta respuesta stackoverflow.com/a/14132912/8682868 .
pzjzeason
vea ValueError: intento de importación relativa más allá del paquete de nivel superior para obtener una explicación detallada sobre este problema.
napuzba
¿Hay alguna manera de evitar hacer importaciones relativas? ¿Como la forma en que PyDev en Eclipse ve todos los paquetes dentro de <PydevProject> / src?
Mushu909

Respuestas:

173

EDITAR: Hay respuestas mejores / más coherentes a esta pregunta en otras preguntas:


¿Por qué no funciona? Es porque python no registra desde dónde se cargó un paquete. Entonces, cuando lo hace python -m test_A.test, básicamente solo descarta el conocimiento que test_A.testrealmente está almacenado package( packagees decir, no se considera un paquete). Intentar from ..A import fooes acceder a información que ya no tiene (es decir, directorios hermanos de una ubicación cargada). Es conceptualmente similar a permitir la entrada from ..os import pathde un archivo math. Esto sería malo porque desea que los paquetes sean distintos. Si necesitan usar algo de otro paquete, deberían referirse a ellos globalmente con from os import pathy dejar que Python resuelva dónde está eso con $PATHy $PYTHONPATH.

Cuando usa python -m package.test_A.test, luego usa from ..A import fooresuelve muy bien porque realiza un seguimiento de lo que hay packagey solo está accediendo a un directorio secundario de una ubicación cargada.

¿Por qué Python no considera que el directorio de trabajo actual sea un paquete? SIN PISTA , pero Dios mío, sería útil.

Multihunter
fuente
2
He editado mi respuesta para referirme a una mejor respuesta a una pregunta que equivale a lo mismo. Solo hay soluciones alternativas. Lo único que realmente he visto funcionar es lo que ha hecho el OP, que es usar el -mindicador y ejecutarlo desde el directorio anterior.
Multihunter
1
Cabe señalar que esta respuesta , desde el enlace proporcionado por Multihunter, no implica el sys.pathpirateo, sino el uso de herramientas de configuración , que es mucho más interesante en mi opinión.
Angelo Cardellicchio
157
import sys
sys.path.append("..") # Adds higher directory to python modules path.

Prueba esto. Trabajó para mi.

jenish Sakhiya
fuente
10
Umm ... ¿cómo podría funcionar esto? ¿Cada archivo de prueba tendría esto?
George Mauer
El problema aquí es si, por ejemplo, A/bar.pyexiste y en foo.pyti from .bar import X.
user1834164
99
Tuve que eliminar el ... de "de ... Una importación ..." después de agregar el sys.path.append ("..")
Jake OPJ
2
Si el script se ejecuta desde fuera del directorio que existe, esto no funcionaría. En cambio, debe ajustar esta respuesta para especificar la ruta absoluta de dicho script .
Manavalan Gajapathy
esta es la mejor opción, menos complicada
Alex R
43

Supuesto:
si está en el packagedirectorio Ay test_Ason paquetes separados.

Conclusión: las
..Aimportaciones solo se permiten dentro de un paquete.

Notas adicionales:
Hacer que las importaciones relativas solo estén disponibles dentro de los paquetes es útil si desea forzar que los paquetes se puedan colocar en cualquier ruta ubicada en sys.path.

EDITAR:

¿Soy el único que piensa que esto es una locura? ¿Por qué en el mundo el directorio de trabajo actual no se considera un paquete? - Multihunter

El directorio de trabajo actual generalmente se encuentra en sys.path. Entonces, todos los archivos allí son importables. Este es el comportamiento desde Python 2 cuando los paquetes aún no existían. Hacer que el directorio en ejecución sea un paquete permitiría importar módulos como "importar .A" y como "importar A", que serían dos módulos diferentes. Tal vez esto sea una inconsistencia a considerar.

Usuario
fuente
86
¿Soy el único que piensa que esto es una locura? ¿Por qué en el mundo el directorio en ejecución no se considera un paquete?
Multihunter
13
No solo es una locura, esto no es útil ... entonces, ¿cómo se ejecutan las pruebas? Claramente, lo que preguntaba el OP y por qué estoy seguro de que muchas personas también están aquí.
George Mauer
El directorio en ejecución generalmente se encuentra en sys.path. Entonces, todos los archivos allí son importables. Este es el comportamiento desde Python 2 cuando los paquetes aún no existían. - Respuesta editada.
Usuario
No sigo la inconsistencia. El comportamiento de python -m package.test_A.testparece hacer lo que se desea, y mi argumento es que ese debería ser el valor predeterminado. Entonces, ¿puedes darme un ejemplo de esta inconsistencia?
Multihunter
Realmente estoy pensando, ¿hay una solicitud de función para esto? Esto es realmente una locura. ¡El estilo C / C ++ #includesería muy útil!
Nicholas Humphrey
29

Ninguna de estas soluciones funcionó para mí en 3.6, con una estructura de carpetas como:

package1/
    subpackage1/
        module1.py
package2/
    subpackage2/
        module2.py

Mi objetivo era importar del módulo1 al módulo2. Lo que finalmente funcionó para mí fue, curiosamente:

import sys
sys.path.append(".")

Tenga en cuenta el punto único en lugar de las soluciones de dos puntos mencionadas hasta ahora.


Editar: Lo siguiente me ayudó a aclarar esto:

import os
print (os.getcwd())

En mi caso, el directorio de trabajo fue (inesperadamente) la raíz del proyecto.

Jason DeMorrow
fuente
2
funciona localmente pero no funciona en la instancia aws ec2, ¿tiene algún sentido?
thebeancounter
Esto también funcionó para mí: en mi caso, el directorio de trabajo también era la raíz del proyecto. Estaba usando un acceso directo de ejecución de un editor de programación (TextMate)
JeremyDouglass
@thebeancounter ¡Lo mismo! Funciona localmente en mi mac pero no funciona en ec2, luego me di cuenta de que estaba ejecutando el comando en un subdirectorio en ec2 y ejecutándolo localmente en la raíz. Una vez que lo ejecuté desde la raíz en ec2 funcionó.
Logan Yang el
Esto también funcionó para mí muy apreciado. Desde ese método sys ahora puedo simplemente llamar al paquete sin la necesidad de ".."
RamWill
sys.path.append(".")funcionó porque lo está llamando en el directorio principal, tenga en cuenta que .siempre representa el directorio donde ejecuta el comando python.
KevinZhou
13

from package.A import foo

Creo que es más claro que

import sys
sys.path.append("..")
Joe Zhow
fuente
44
es más legible seguro pero aún necesita sys.path.append(".."). probado en python 3.6
MFA
Igual que las respuestas anteriores
nrofis el
12

Como sugiere la respuesta más popular, básicamente es porque tu PYTHONPATHo sys.pathincluye .pero no tu ruta a tu paquete. Y la importación relativa es relativa a su directorio de trabajo actual, no el archivo donde ocurre la importación; extrañamente.

Puede solucionar esto cambiando primero su importación relativa a absoluta y luego comenzando con:

PYTHONPATH=/path/to/package python -m test_A.test

O forzar la ruta de Python cuando se llama de esta manera, porque:

Con python -m test_A.testque estés ejecutando test_A/test.pycon __name__ == '__main__'y__file__ == '/absolute/path/to/test_A/test.py'

Eso significa que test.pypodría usar su absoluto importsemi-protegido en la condición de caso principal y también hacer una manipulación de ruta Python única:

from os import path

def main():

if __name__ == '__main__':
    import sys
    sys.path.append(path.join(path.dirname(__file__), '..'))
    from A import foo

    exit(main())
dlamblin
fuente
8

Editar: 2020-05-08: Parece que el sitio web que cité ya no está controlado por la persona que escribió el consejo, por lo que estoy eliminando el enlace al sitio. Gracias por dejarme saber baxx.


Si alguien todavía está luchando un poco después de las excelentes respuestas ya proporcionadas, encontré consejos en un sitio web que ya no está disponible.

Cita esencial del sitio que mencioné:

"Lo mismo se puede especificar mediante programación de esta manera:

sistema de importación

sys.path.append ('..')

Por supuesto, el código anterior debe escribirse antes de la otra declaración de importación .

Es bastante obvio que tiene que ser así, pensando en ello después del hecho. Intenté usar sys.path.append ('..') en mis pruebas, pero me encontré con el problema publicado por OP. Al agregar la definición de importación y sys.path antes de mis otras importaciones, pude resolver el problema.

Mierpo
fuente
el enlace que publicaste está muerto.
baxx
Gracias por hacérmelo saber. Parece que el nombre de dominio ya no está controlado por la misma persona. He eliminado el enlace.
Mierpo
5

si tiene una __init__.pycarpeta superior, puede inicializar la importación como import file/path as aliasen ese archivo de inicio Luego puede usarlo en scripts inferiores como:

import alias
pelos
fuente
0

En mi humilde opinión, entiendo esta pregunta de esta manera:

[CASO 1] Cuando comienzas una importación absoluta como

python -m test_A.test

o

import test_A.test

o

from test_A import test

en realidad está configurando el ancla de importación para que sea test_A, en otras palabras, el paquete de nivel superior es test_A. Entonces, cuando tenemos test.py do from ..A import xxx, estás escapando del ancla, y Python no permite esto.

[CASO 2] Cuando lo haces

python -m package.test_A.test

o

from package.test_A import test

su ancla se convierte package, por package/test_A/test.pylo from ..A import xxxque hacer no escapa del ancla (todavía dentro de la packagecarpeta), y Python felizmente acepta esto.

En breve:

  • La importación absoluta cambia el ancla actual (= redefine cuál es el paquete de nivel superior);
  • La importación relativa no cambia el ancla sino que se limita a ella.

Además, podemos usar el nombre de módulo completo (FQMN) para inspeccionar este problema.

Verifique FQMN en cada caso:

  • [CASO2] test.__name__=package.test_A.test
  • [CASO1] test.__name__=test_A.test

Entonces, para CASE2, un from .. import xxxdará como resultado un nuevo módulo con FQMN = package.xxx, lo cual es aceptable.

Mientras que para CASE1, ..desde adentro from .. import xxxsaltará fuera del nodo de inicio (ancla) de test_A, y Python NO lo permite.

Jimm Chen
fuente
2
Esto es mucho más complicado de lo que debe ser. Demasiado para el Zen de Python.
AtilioA
0

No estoy seguro en Python 2.x, pero en Python 3.6, suponiendo que está intentando ejecutar toda la suite, solo tiene que usar -t

-t, --top-level-directory directory Directorio de nivel superior del proyecto (predeterminado para iniciar el directorio)

Entonces, en una estructura como

project_root
  |
  |----- my_module
  |          \
  |           \_____ my_class.py
  |
  \ tests
      \___ test_my_func.py

Por ejemplo, se podría usar:

python3 unittest discover -s /full_path/project_root/tests -t /full_path/project_root/

Y todavía importa el my_module.my_classsin dramas importantes.

Andre de Miranda
fuente