¿Cómo puedo hacer importaciones relativas en Python?

10 minutos de lectura

Avatar de usuario de Joril
Joril

Imagina esta estructura de directorios:

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

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

Lo intenté from ..sub2 import mod2pero obtengo un “Intento de importación relativa sin paquete”.

Busqué en Google, pero solo encontré “sys.path trucos de manipulación. ¿No hay una manera limpia?


Toda mi __init__.pyestán actualmente vacíos

Estoy tratando de hacer esto porque sub2 contiene clases que se comparten entre subpaquetes (sub1, subXetc.).

El comportamiento que estoy buscando es el mismo que se describe en PEP 366 (gracias Juan B).

  • Recomiendo actualizar su pregunta para que quede más claro que está describiendo el problema abordado en PEP 366.

    – Juan B.

    16 de septiembre de 2008 a las 19:36

  • 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

    23 de mayo de 2012 a las 4:05

  • Para aquellos que deseen cargar un módulo ubicado en una ruta arbitraria, vean esto: stackoverflow.com/questions/67631/…

    –Evgeni Sergeev

    8 de junio de 2014 a las 6:29

  • En una nota relacionada, Python 3 cambiará el manejo predeterminado de las importaciones para que sea absoluto de forma predeterminada; las importaciones relativas deberán especificarse explícitamente.

    – Ross

    30 de marzo de 2015 a las 23:28

Avatar de usuario de John B.
Juan B.

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

De PEP 328:

Las importaciones relativas usan el atributo __name__ de un módulo para determinar la posición de ese módulo en la jerarquía del paquete. Si el nombre del módulo no contiene ninguna información del paquete (por ejemplo, está configurado como ‘__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.

Según Nick Coghlan, la alternativa recomendada es ejecutar el módulo dentro del paquete usando el modificador -m.

  • La alternativa recomendada es ejecutar módulos dentro de paquetes usando el -m switch, en lugar de especificar su nombre de archivo directamente.

    – ncoghlán

    23 de febrero de 2011 a las 4:25

  • No entiendo: ¿dónde está la respuesta aquí? ¿Cómo se pueden importar módulos en una estructura de directorios de este tipo?

    – Tomás

    29 de septiembre de 2012 a las 16:34

  • @Tom: En este caso, mod1 sería from sub2 import mod2. Luego, para ejecutar mod1, desde dentro de la aplicación, haz python -m sub1.mod1.

    – Xiong Chiamiov

    20 de noviembre de 2012 a las 6:06

  • @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 la línea de comandos de python?

    – LarsH

    29 de enero de 2013 a las 16:58

  • “Todo el mundo parece querer decirte lo que deberías estar haciendo en lugar de simplemente responder a la pregunta”. Bien, bien. Esa es la forma de obtener ayuda para escribir software debería ¡ser! Cuando voy en contra de las buenas prácticas, Quiero saber.

    – jpmc26

    3 de abril de 2019 a las 3:52

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

Hago las importaciones relativas como from ..sub2 import mod2
y luego, si quiero correr mod1.py luego voy al directorio principal de app y ejecute el módulo usando el interruptor python -m como python -m app.sub1.mod1.

La verdadera razón por la que ocurre este problema con las importaciones relativas es que las importaciones relativas funcionan tomando el __name__ propiedad del módulo. Si el módulo se está ejecutando directamente, entonces __name__ se establece en __main__ y no contiene ninguna información sobre la estructura del paquete. Y, es por eso que Python se queja de la relative import in non-package error.

Entonces, al usar el interruptor -m, 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 al hacer importaciones relativas. Y, después de leer todas las respuestas anteriores, todavía no pude descubrir cómo resolverlo, de una manera limpia, sin necesidad de poner el 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 de importaciones relativas, porque pasar por PEP realmente no es divertido.

  • 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 fue la falta de acceso a los paquetes externos cuando se ejecutaba directamente como script, algo -m fue diseñado para resolver.

    – Mestre León

    7 de noviembre de 2013 a las 3:40

  • También tome nota: esta respuesta fue 5 años después de la pregunta. Estas características no estaban disponibles en ese momento.

    – Jeremy Kun

    01/04/2014 a las 21:05

  • Si desea importar un módulo desde el mismo directorio, puede hacerlo from . import some_module.

    – Rotareti

    16/09/2016 a las 23:47

  • Esta es la respuesta que me ayudó y también me ayudó a condensar mi pensamiento en esto: Para ejecutar una secuencia de comandos de Python que contiene importaciones relativas, debo ejecutar la secuencia de comandos como un módulo mientras $ PWD es su directorio padre como $ python -m app.main. Para mayor claridad, $ python -m <main_directory>.<script_with_relative_imports>

    – Jesse H.

    15 mayo 2021 a las 15:33


main.py
setup.py
app/ ->
    __init__.py
    package_a/ ->
       __init__.py
       module_a.py
    package_b/ ->
       __init__.py
       module_b.py
  1. Tu corres 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 tengas app en tu PYTHONPATH. main.py podría estar en cualquier lugar entonces.

Así que escribes un setup.py para copiar (instalar) todo el paquete de la aplicación y los subpaquetes en las carpetas de python del sistema de destino, y main.py a las carpetas de secuencias de comandos del sistema de destino.

  • Excelente respuesta ¿Hay alguna forma de importar de esa manera sin instalar el paquete en PYTHONPATH?

    – auraham

    27 de julio de 2012 a las 16:34

  • Lectura adicional sugerida: blog.habnab.it/blog/2013/07/21/python-packages-and-you

    – nosklo

    17/10/2013 a las 11:40

  • luego, 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

    16 dic 2016 a las 11:30


avatar de usuario de lesnik
Lesnik

“Guido ve los scripts en ejecución dentro de un paquete como un antipatrón” (rechazado
PEP-3122)

Pasé mucho 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 hay.

Avatar de usuario de Роман Арсеньев
Роман Арсеньев

Esto está resuelto al 100%:

  • aplicación/
    • principal.py
  • ajustes/
    • escenarios_locales.py

Importe settings/local_setting.py en app/main.py:

principal.py:

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


try:
    from local_settings import *
except ImportError:
    print('No Import')

  • ¡gracias! todas las personas me obligaron a ejecutar mi secuencia de comandos de manera diferente en lugar de decirme cómo resolverlo dentro de la secuencia de comandos. Pero tuve que cambiar el código para usar sys.path.insert(0, "../settings") y luego from local_settings import *

    – Vit Bernatik

    4 de noviembre de 2016 a las 20:22


  • ¿Puede explicar su respuesta, por favor? Por ejemplo, por qué ¿Funciona? Cómo ¿Funciona? ¿Cuál es la esencia? “La explicación es vital para una buena respuesta.”_. (Pero ***** ***** ***** sin ***** ***** ***** “Editar:”, “Actualizar:”, o similar – la respuesta debería aparecer como si hubiera sido escrita hoy)

    -Peter Mortensen

    Hace 19 horas

Avatar de usuario de Peter Mortensen
Pedro Mortensen

Explicación de la respuesta de nosklo con ejemplos:

Nota: todo __init__.py los archivos están vacíos.

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

app/paquete_a/fun_a.py

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

app/paquete_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()

principal.py

from app.package_b import fun_b
fun_b.print_b()

Si tu corres python main.py vuelve:

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 archivo en la carpeta package_b archivo usado en la carpeta package_a, que es lo que quieres. ¿¿Bien??

  • ¡gracias! todas las personas me obligaron a ejecutar mi secuencia de comandos de manera diferente en lugar de decirme cómo resolverlo dentro de la secuencia de comandos. Pero tuve que cambiar el código para usar sys.path.insert(0, "../settings") y luego from local_settings import *

    – Vit Bernatik

    4 de noviembre de 2016 a las 20:22


  • ¿Puede explicar su respuesta, por favor? Por ejemplo, por qué ¿Funciona? Cómo ¿Funciona? ¿Cuál es la esencia? “La explicación es vital para una buena respuesta.”_. (Pero ***** ***** ***** sin ***** ***** ***** “Editar:”, “Actualizar:”, o similar – la respuesta debería aparecer como si hubiera sido escrita hoy)

    -Peter Mortensen

    Hace 19 horas

Avatar de usuario de Peter Mortensen
Pedro Mortensen

Usar:

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.

  • 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

    16 de julio de 2009 a las 21:20

  • Probablemente, sys.path.append(ruta) debería reemplazarse con sys.path.insert(0, ruta) y sys.path[-1] debe ser reemplazado 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

    16 de junio de 2010 a las 8:24

  • ¡Estoy de acuerdo! A veces, otras importaciones relativas tendrán precedencia. Usar sys.path.insertar

    – iEléctrico

    19 de junio de 2010 a las 7:13

  • ¿Cómo replicaría el comportamiento de from x import y (o *)?

    – levesque

    7 de diciembre de 2010 a las 19:26

  • No está claro, especifique el uso completo de este script para resolver el problema de OP.

    – mrgloom

    24 de septiembre de 2018 a las 13:12

¿Ha sido útil esta solución?