¿Cómo hacer importaciones relativas en Python?

527

Imagine esta estructura de directorios:

app/
   __init__.py
   sub1/
      __init__.py
      mod1.py
   sub2/
      __init__.py
      mod2.py

Estoy codificando mod1y necesito importar algo mod2. ¿Cómo debería hacerlo?

Lo intenté from ..sub2 import mod2pero obtengo un "Intento de importación relativa en un paquete no"

Busqué en Google pero encontré solo sys.pathhacks de " manipulación". ¿No hay una manera limpia?


Editar: todos mis __init__.py's están actualmente vacíos

Edit2: Estoy tratando de hacer esto porque sub2 contiene clases que se comparten a través de paquetes de sub ( sub1, subX, etc.).

Edit3: El comportamiento que estoy buscando es el mismo que se describe en PEP 366 (gracias John B)

Joril
fuente
8
Recomiendo actualizar su pregunta para que quede más claro que está describiendo el problema abordado en PEP 366.
John B
2
Es una explicación larga, pero verifique aquí: stackoverflow.com/a/10713254/1267156 Respondí una pregunta muy similar. Tuve este mismo problema hasta anoche.
Sevvy325
3
Para aquellos que desean cargar un módulo ubicado en una ruta arbitraria, vea esto: stackoverflow.com/questions/67631/…
Evgeni Sergeev
2
En una nota relacionada, Python 3 cambiará el manejo predeterminado de las importaciones para que sea absoluto por defecto; las importaciones relativas deberán especificarse explícitamente.
Ross

Respuestas:

337

Todo el mundo parece querer decirte lo que debes hacer en lugar de solo responder la pregunta.

El problema es que está ejecutando el módulo como '__main__' pasando el mod1.py como argumento al intérprete.

Desde PEP 328 :

Las importaciones relativas utilizan el atributo __name__ 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 del paquete (por ejemplo, se establece en '__main__'), las importaciones relativas se resuelven como si el módulo fuera un módulo de nivel superior, independientemente de dónde se encuentre realmente el módulo en el sistema de archivos.

En Python 2.6, están agregando la capacidad de hacer referencia a módulos en relación con el módulo principal. PEP 366 describe el cambio.

Actualización : según Nick Coghlan, la alternativa recomendada es ejecutar el módulo dentro del paquete utilizando el modificador -m.

John B
fuente
2
La respuesta aquí implica jugar con sys.path en cada punto de entrada a su programa. Supongo que esa es la única forma de hacerlo.
Nick Retallack
76
La alternativa recomendada es ejecutar módulos dentro de paquetes usando el -mconmutador, en lugar de especificar su nombre de archivo directamente.
ncoghlan
127
No entiendo: ¿dónde está la respuesta aquí? ¿Cómo se pueden importar módulos en dicha estructura de directorios?
Tom
27
@ Tom: En este caso, mod1 lo haría from sub2 import mod2. Luego, para ejecutar mod1, desde la aplicación, hazlo python -m sub1.mod1.
Xiong Chiamiov
11
@ XiongChiamiov: ¿significa esto que no puede hacerlo si su Python está incrustado en una aplicación, por lo que no tiene acceso a los interruptores de línea de comando de Python?
LarsH
130

Aquí está la solución que funciona para mí:

Hago las importaciones relativas como from ..sub2 import mod2 y luego, si quiero ejecutar, mod1.pyentonces voy al directorio principal de appy ejecuto el módulo usando el modificador python -m como python -m app.sub1.mod1.

La verdadera razón por la cual este problema ocurre con las importaciones relativas, es que las importaciones relativas funcionan tomando la __name__propiedad del módulo. Si el módulo se está ejecutando directamente, __name__se establece en __main__y no contiene ninguna información sobre la estructura del paquete. Y es por eso que Python se queja del relative import in non-packageerror.

Entonces, al usar el modificador -m, usted proporciona la información de la estructura del paquete a Python, a través de la cual puede resolver las importaciones relativas con éxito.

Me he encontrado con este problema muchas veces mientras hacía importaciones relativas. Y, después de leer todas las respuestas anteriores, aún no pude encontrar la manera de resolverlo, de una manera limpia, sin necesidad de poner código repetitivo en todos los archivos. (Aunque algunos de los comentarios fueron realmente útiles, gracias a @ncoghlan y @XiongChiamiov)

Espero que esto ayude a alguien que está luchando con un problema relativo de importación, porque pasar por PEP realmente no es divertido.

Pankaj
fuente
99
La mejor respuesta en mi humilde opinión: no solo explica por qué OP tuvo el problema, sino que también encuentra una manera de resolverlo sin cambiar la forma en que sus módulos importan . Después de todo, las importaciones relativas de OP estaban bien. El culpable era la falta de acceso a paquetes externos cuando se ejecutaba directamente como script, algo -mestaba diseñado para resolver.
MestreLion
26
También tome nota: esta respuesta fue 5 años después de la pregunta. Estas características no estaban disponibles en ese momento.
JeremyKun
1
Si desea importar un módulo desde el mismo directorio, puede hacerlo from . import some_module.
Rotareti
124
main.py
setup.py
app/ ->
    __init__.py
    package_a/ ->
       __init__.py
       module_a.py
    package_b/ ->
       __init__.py
       module_b.py
  1. Ejecutar python main.py.
  2. main.py hace: import app.package_a.module_a
  3. module_a.py hace import app.package_b.module_b

Alternativamente, 2 o 3 podrían usar: from app.package_a import module_a

Eso funcionará mientras lo tenga appen su PYTHONPATH. main.pypodría estar en cualquier lugar entonces.

Por lo tanto, escribe un setup.pypara copiar (instalar) el paquete completo de la aplicación y los subpaquetes en las carpetas python del sistema de destino y main.pyen las carpetas de script del sistema de destino.

nosklo
fuente
3
Excelente respuesta ¿Hay alguna manera de importar de esa manera sin instalar el paquete en PYTHONPATH?
auraham
44
Lectura adicional sugerida: blog.habnab.it/blog/2013/07/21/python-packages-and-you
nosklo
66
entonces, un día, necesita cambiar el nombre de la aplicación a test_app. ¿qué pasaría? Deberá cambiar todos los códigos fuente, importar app.package_b.module_b -> test_app.package_b.module_b. esta es una práctica absolutamente MALA ... Y deberíamos intentar usar la importación relativa dentro del paquete.
Spybdai
49

"Guido ve los scripts en ejecución dentro de un paquete como un antipatrón" (rechazó PEP-3122 )

He pasado tanto tiempo tratando de encontrar una solución, leyendo publicaciones relacionadas aquí en Stack Overflow y diciéndome a mí mismo "¡debe haber una mejor manera!". Parece que no la hay.

lesnik
fuente
10
Nota: el ya mencionado pep-366 (creado aproximadamente al mismo tiempo que pep-3122 ) proporciona las mismas capacidades pero usa una implementación diferente compatible con versiones anteriores, es decir, si desea ejecutar un módulo dentro de un paquete como un script y usar importaciones relativas explícitas en él, puede ejecutarlo usando -mswitch: python -m app.sub1.mod1o invocar app.sub1.mod1.main()desde un script de nivel superior (por ejemplo, generado a partir de los puntos de entrada de setuptools definidos en setup.py).
jfs
+1 para usar herramientas de configuración y puntos de entrada: es una forma adecuada de configurar scripts que se ejecutarán desde el exterior, en una ubicación bien definida, en lugar de piratear sin fin PYTHONPATH
RecencyEffect
38

Esto se resuelve al 100%:

  • aplicación /
    • main.py
  • ajustes /
    • local_setings.py

Configuración de importación / local_setting.py en app / main.py:

main.py:

import sys
sys.path.insert(0, "../settings")


try:
    from local_settings import *
except ImportError:
    print('No Import')
Роман Арсеньев
fuente
2
¡gracias! todas las personas me obligaban a ejecutar mi script de manera diferente en lugar de decirme cómo resolverlo dentro del script. Pero tuve que cambiar el código para usar sys.path.insert(0, "../settings")y luegofrom local_settings import *
Vit Bernatik
25
def import_path(fullpath):
    """ 
    Import a file with full path specification. Allows one to
    import from anywhere, something __import__ does not do. 
    """
    path, filename = os.path.split(fullpath)
    filename, ext = os.path.splitext(filename)
    sys.path.append(path)
    module = __import__(filename)
    reload(module) # Might be out of date
    del sys.path[-1]
    return module

Estoy usando este fragmento para importar módulos desde rutas, espero que ayude

iElectric
fuente
2
Estoy usando este fragmento, combinado con el módulo imp (como se explica aquí [1]) con gran efecto. [1]: stackoverflow.com/questions/1096216/…
Xiong Chiamiov
77
Probablemente, sys.path.append (ruta) debe reemplazarse con sys.path.insert (0, ruta), y sys.path [-1] debe reemplazarse con sys.path [0]. De lo contrario, la función importará el módulo incorrecto, si ya hay un módulo con el mismo nombre en la ruta de búsqueda. Por ejemplo, si hay "some.py" en el directorio actual, import_path ("/ imports / some.py") importará el archivo incorrecto.
Alex Che
¡Estoy de acuerdo! A veces otras importaciones relativas harán precedencia. Use sys.path.insert
iElectric
¿Cómo replicaría el comportamiento de from x import y (o *)?
levesque
No está claro, especifique el uso completo de este script para resolver el problema de OP.
mrgloom
21

explicación de nosklo's respuesta con ejemplos

nota: todos los __init__.pyarchivos están vacíos.

main.py
app/ ->
    __init__.py
    package_a/ ->
       __init__.py
       fun_a.py
    package_b/ ->
       __init__.py
       fun_b.py

app / package_a / fun_a.py

def print_a():
    print 'This is a function in dir package_a'

app / package_b / fun_b.py

from app.package_a.fun_a import print_a
def print_b():
    print 'This is a function in dir package_b'
    print 'going to call a function in dir package_a'
    print '-'*30
    print_a()

main.py

from app.package_b import fun_b
fun_b.print_b()

si lo ejecutas $ python main.pydevuelve:

This is a function in dir package_b
going to call a function in dir package_a
------------------------------
This is a function in dir package_a
  • main.py hace: from app.package_b import fun_b
  • fun_b.py hace from app.package_a.fun_a import print_a

así que el archivo en la carpeta package_butiliza el archivo en la carpeta package_a, que es lo que quieres. ¿¿Derecha??

suhailvs
fuente
12

Desafortunadamente, este es un truco de sys.path, pero funciona bastante bien.

Encontré este problema con otra capa: ya tenía un módulo con el nombre especificado, pero era el módulo incorrecto.

lo que quería hacer era lo siguiente (el módulo desde el que estaba trabajando era module3):

mymodule\
   __init__.py
   mymodule1\
      __init__.py
      mymodule1_1
   mymodule2\
      __init__.py
      mymodule2_1


import mymodule.mymodule1.mymodule1_1  

Tenga en cuenta que ya instalé mymodule, pero en mi instalación no tengo "mymodule1"

y obtendría un ImportError porque intentaba importar desde mis módulos instalados.

Traté de hacer un sys.path.append, y eso no funcionó. Lo que funcionó fue un sys.path.insert

if __name__ == '__main__':
    sys.path.insert(0, '../..')

Fue un truco, ¡pero todo funcionó! Por lo tanto, tenga en cuenta que si desea que su decisión anule otras rutas , ¡debe usar sys.path.insert (0, pathname) para que funcione! Este fue un punto muy frustrante para mí, mucha gente dice que use la función "agregar" a sys.path, pero eso no funciona si ya tiene un módulo definido (me parece un comportamiento muy extraño)

Garrett Berg
fuente
sys.path.append('../')funciona bien para mí (Python 3.5.2)
Nister
Creo que esto está bien, ya que localiza el pirateo del ejecutable y no afecta a otros módulos que pueden depender de sus paquetes.
Tom Russell
10

Permítanme poner esto aquí para mi propia referencia. Sé que no es un buen código Python, pero necesitaba un script para un proyecto en el que estaba trabajando y quería colocar el script en un scriptsdirectorio.

import os.path
import sys
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
milkypostman
fuente
9

Como @EvgeniSergeev dice en los comentarios al OP, puede importar código de un .pyarchivo en una ubicación arbitraria con:

import imp

foo = imp.load_source('module.name', '/path/to/file.py')
foo.MyClass()

Esto se toma de esta respuesta SO .

LondonRob
fuente
2

De Python doc ,

En Python 2.5, puede cambiar el comportamiento de importación a importaciones absolutas utilizando una from __future__ import absolute_importdirectiva. Este comportamiento de importación absoluta se convertirá en el predeterminado en una versión futura (probablemente Python 2.7). Una vez que las importaciones absolutas sean las predeterminadas, import stringsiempre encontrará la versión de la biblioteca estándar. Se sugiere que los usuarios comiencen a usar importaciones absolutas tanto como sea posible, por lo que es preferible comenzar a escribir from pkg import stringen su código

jung rhew
fuente
1

Descubrí que es más fácil establecer la variable de entorno "PYTHONPATH" en la carpeta superior:

bash$ export PYTHONPATH=/PATH/TO/APP

entonces:

import sub1.func1
#...more import

por supuesto, PYTHONPATH es "global", pero todavía no me planteó problemas.

Andrew_1510
fuente
Esto es esencialmente cómo le virtualenvpermite administrar sus declaraciones de importación.
byxor
1

Además de lo que dijo John B, parece que establecer la __package__variable debería ayudar, en lugar de cambiar lo __main__que podría arruinar otras cosas. Pero por lo que pude probar, no funciona completamente como debería.

Tengo el mismo problema y ni PEP 328 ni 366 resuelven el problema por completo, ya que ambos, al final del día, necesitan que se incluya el encabezado del paquete en sys.path , por lo que pude entender.

También debo mencionar que no encontré cómo formatear la cadena que debería ir a esas variables. ¿Es "package_head.subfolder.module_name"o qué?

Gabriel
fuente
0

Debe agregar la ruta del módulo a PYTHONPATH:

export PYTHONPATH="${PYTHONPATH}:/path/to/your/module/"
Giorgos Myrianthous
fuente
1
Esto es más o menos lo mismo que manipular sys.path, ya que sys.pathse inicializa desdePYTHONPATH
Joril
@Joril Eso es correcto pero sys.pathnecesita ser codificado en el código fuente en contraste con lo PYTHONPATHque es una variable de entorno y se puede exportar.
Giorgos Myrianthous