Importaciones relativas en Python 3


714

Quiero importar una función desde otro archivo en el mismo directorio.

A veces me funciona, from .mymodule import myfunctionpero a veces me sale un:

SystemError: Parent module '' not loaded, cannot perform relative import

A veces funciona from mymodule import myfunction, pero a veces también obtengo un:

SystemError: Parent module '' not loaded, cannot perform relative import

No entiendo la lógica aquí, y no pude encontrar ninguna explicación. Esto se ve completamente al azar.

¿Podría alguien explicarme cuál es la lógica detrás de todo esto?


76
Esto significa que está ejecutando un módulo dentro del paquete como un script. Solo ejecuta scripts desde fuera del paquete.
Martijn Pieters

3
Probablemente deberías definir las condiciones que tienes aquellas 'a veces' que mencionas. Entiendo que no quiere decir que tenga errores aleatorios.
joaquin

15
@MartijnPieters: bueno, desafortunadamente, este módulo debe estar dentro del paquete, y también debe ser ejecutable como un script, a veces. ¿Alguna idea de cómo podría lograr eso?
John Smith Opcional

22
@JohnSmithOptional: Mezclar scripts dentro de paquetes es complicado y debe evitarse si es posible. Utilice un script de envoltura que importe el paquete y ejecute su función 'scripty' en su lugar.
Martijn Pieters

3
Parece desafortunado Creé un módulo central con clases / métodos que pueden analizar / analizar un cierto tipo de archivo, y también tengo (principalmente para mí) módulos secundarios y scripts separados que lo importan, estos pueden masajear / convertir esos archivos. Pero también me gusta poder entregar ese archivo de núcleo único (no un paquete complejo completo) al usuario final para que pueda colocarlo fácilmente al lado de su archivo y ejecutarlo. En ese "modo de secuencia de comandos", analiza y analiza el archivo y la codificación, cuenta varios campos / valores / caracteres especiales y proporciona un informe. Pero en realidad no modifica el archivo. ¿Anti-patrón?
Jon Coombs

Respuestas:


528

desafortunadamente, este módulo necesita estar dentro del paquete, y también debe ser ejecutable como un script, a veces. ¿Alguna idea de cómo podría lograr eso?

Es bastante común tener un diseño como este ...

main.py
mypackage/
    __init__.py
    mymodule.py
    myothermodule.py

... con algo mymodule.pyasí ...

#!/usr/bin/env python3

# Exported function
def as_int(a):
    return int(a)

# Test function for module  
def _test():
    assert as_int('1') == 1

if __name__ == '__main__':
    _test()

... myothermodule.pyasí ...

#!/usr/bin/env python3

from .mymodule import as_int

# Exported function
def add(a, b):
    return as_int(a) + as_int(b)

# Test function for module  
def _test():
    assert add('1', '1') == 2

if __name__ == '__main__':
    _test()

... y main.pyasí ...

#!/usr/bin/env python3

from mypackage.myothermodule import add

def main():
    print(add('1', '1'))

if __name__ == '__main__':
    main()

... que funciona bien cuando ejecuta main.pyo mypackage/mymodule.py, pero falla mypackage/myothermodule.py, debido a la importación relativa ...

from .mymodule import as_int

La forma en que se supone que debes ejecutarlo es ...

python3 -m mypackage.myothermodule

... pero es algo detallado y no se mezcla bien con una línea shebang como #!/usr/bin/env python3.

La solución más simple para este caso, suponiendo que el nombre mymodulesea ​​globalmente único, sería evitar el uso de importaciones relativas, y simplemente usar ...

from mymodule import as_int

... aunque, si no es único, o la estructura de su paquete es más compleja, deberá incluir el directorio que contiene el directorio de su paquete PYTHONPATHy hacerlo así ...

from mypackage.mymodule import as_int

... o si desea que funcione "fuera de la caja", puede extraer el PYTHONPATHcódigo primero con esto ...

import sys
import os

PACKAGE_PARENT = '..'
SCRIPT_DIR = os.path.dirname(os.path.realpath(os.path.join(os.getcwd(), os.path.expanduser(__file__))))
sys.path.append(os.path.normpath(os.path.join(SCRIPT_DIR, PACKAGE_PARENT)))

from mypackage.mymodule import as_int

Es un poco doloroso, pero hay una pista de por qué en un correo electrónico escrito por cierto Guido van Rossum ...

Estoy -1 en esto y en cualquier otro giro propuesto de la __main__ maquinaria. El único caso de uso parece estar ejecutando scripts que viven dentro del directorio de un módulo, que siempre he visto como un antipatrón. Para hacerme cambiar de opinión tendrías que convencerme de que no es así.

Si ejecutar scripts dentro de un paquete es un antipatrón o no es subjetivo, pero personalmente me parece realmente útil en un paquete que tengo que contiene algunos widgets wxPython personalizados, por lo que puedo ejecutar el script para cualquiera de los archivos fuente para mostrar wx.Framesolo ese widget para fines de prueba.


77
Una mejor manera de obtener SCRIPTDIR se da en un comentario de Importar un módulo desde una ruta relativa como os.path.realpath(os.path.dirname(inspect.getfile(inspect.currentframe())))si estuviera seguro de que su módulo siempre tiene una propiedad adecuada fileque también podría usar os.path.realpath(os.path.dirname(__file__)).
marcz

2
Puede ampliar su PYTHONPATH aplicando un fragmento de código más corto y legible: sys.path.append( os.path.join( os.path.dirname(__file__), os.path.pardir ) )
Alex-Bogdanov

12
...which I've always seen as an antipattern.No veo cómo es un anti patrón ... Parece que sería muy conveniente simplemente hacer que las importaciones relativas funcionen intuitivamente. Solo quiero poder importar cosas que sé que están en el mismo directorio. Me pregunto cuál fue su razonamiento
YungGun

99
Guido ataca de nuevo: eliminando cosas que podrían haber sido útiles. Bueno, eso ya no sucederá.
javadba

44
Esta es la cosa más triste que he visto sobre Python.
AtilioA

263

Explicación

De 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 algún momento, PEP 338 entró en conflicto con PEP 328 :

... las importaciones relativas dependen de __name__ para determinar la posición del módulo actual en la jerarquía de paquetes. En un módulo principal, el valor de __name__ siempre es '__main__' , por lo que las importaciones relativas explícitas siempre fallarán (ya que solo funcionan para un módulo dentro de un paquete)

y para abordar el problema, PEP 366 introdujo la variable de nivel superior __package__:

Al agregar un nuevo atributo de nivel de módulo, este PEP permite que las importaciones relativas funcionen automáticamente si el módulo se ejecuta utilizando el modificador -m . Una pequeña cantidad de repeticiones en el módulo en sí permitirá que las importaciones relativas funcionen cuando el archivo se ejecute por nombre. [...] Cuando [el atributo] está presente, las importaciones relativas se basarán en este atributo en lugar del atributo __name__ del módulo . [...] Cuando el módulo principal se especifica por su nombre de archivo, el atributo __paquete__ se establecerá en Ninguno . [...] Cuando el sistema de importación encuentra una importación relativa explícita en un módulo sin __paquete__ establecido (o con él establecido en Ninguno), calculará y almacenará el valor correcto (__name __. rpartition ('.') [0] para módulos normales y __name__ para módulos de inicialización de paquetes)

(énfasis mío)

Si __name__es '__main__', __name__.rpartition('.')[0]devuelve una cadena vacía. Es por eso que hay una cadena literal vacía en la descripción del error:

SystemError: Parent module '' not loaded, cannot perform relative import

La parte relevante de la PyImport_ImportModuleLevelObjectfunción de CPython :

if (PyDict_GetItem(interp->modules, package) == NULL) {
    PyErr_Format(PyExc_SystemError,
            "Parent module %R not loaded, cannot perform relative "
            "import", package);
    goto error;
}

CPython genera esta excepción si no pudo encontrar package(el nombre del paquete) en interp->modules(accesible como sys.modules). Dado que sys.moduleses "un diccionario que asigna nombres de módulos a módulos que ya se han cargado" , ahora está claro que el módulo principal debe importarse de forma explícita antes de realizar la importación relativa .

Nota: El parche del problema 18018 ha agregado otro ifbloque , que se ejecutará antes del código anterior:

if (PyUnicode_CompareWithASCIIString(package, "") == 0) {
    PyErr_SetString(PyExc_ImportError,
            "attempted relative import with no known parent package");
    goto error;
} /* else if (PyDict_GetItem(interp->modules, package) == NULL) {
    ...
*/

Si package(igual que el anterior) es una cadena vacía, el mensaje de error será

ImportError: attempted relative import with no known parent package

Sin embargo, solo verá esto en Python 3.6 o posterior.

Solución n. ° 1: Ejecute su script usando -m

Considere un directorio (que es un paquete de Python ):

.
├── package
│   ├── __init__.py
│   ├── module.py
│   └── standalone.py

Todos los archivos en el paquete comienzan con las mismas 2 líneas de código:

from pathlib import Path
print('Running' if __name__ == '__main__' else 'Importing', Path(__file__).resolve())

Incluyo estas dos líneas solo para que el orden de las operaciones sea obvio. Podemos ignorarlos por completo, ya que no afectan la ejecución.

__init__.py y module.py contienen solo esas dos líneas (es decir, están efectivamente vacías).

standalone.py además intenta importar module.py a través de la importación relativa:

from . import module  # explicit relative import

Somos conscientes de que /path/to/python/interpreter package/standalone.pyfallará. Sin embargo, podemos ejecutar el módulo con la -mopción de línea de comando que "buscará sys.pathel módulo nombrado y ejecutará su contenido como __main__módulo" :

vaultah@base:~$ python3 -i -m package.standalone
Importing /home/vaultah/package/__init__.py
Running /home/vaultah/package/standalone.py
Importing /home/vaultah/package/module.py
>>> __file__
'/home/vaultah/package/standalone.py'
>>> __package__
'package'
>>> # The __package__ has been correctly set and module.py has been imported.
... # What's inside sys.modules?
... import sys
>>> sys.modules['__main__']
<module 'package.standalone' from '/home/vaultah/package/standalone.py'>
>>> sys.modules['package.module']
<module 'package.module' from '/home/vaultah/package/module.py'>
>>> sys.modules['package']
<module 'package' from '/home/vaultah/package/__init__.py'>

-mhace todas las cosas de importación por usted y las configura automáticamente __package__, pero puede hacerlo usted mismo en el

Solución n. ° 2: configure __package__ manualmente

Trátelo como una prueba de concepto en lugar de una solución real. No es adecuado para su uso en código del mundo real.

PEP 366 tiene una solución a este problema, sin embargo, está incompleto, porque la configuración por __package__sí sola no es suficiente. Deberá importar al menos N paquetes anteriores en la jerarquía del módulo, donde N es el número de directorios principales (en relación con el directorio del script) que se buscarán para el módulo que se importará.

Así,

  1. Agregue el directorio padre del enésimo predecesor del módulo actual asys.path

  2. Eliminar el directorio del archivo actual de sys.path

  3. Importe el módulo principal del módulo actual utilizando su nombre completo

  4. Establecer __package__en el nombre completo de 2

  5. Realizar la importación relativa

Tomaré prestados archivos de la Solución # 1 y agregaré algunos subpaquetes más:

package
├── __init__.py
├── module.py
└── subpackage
    ├── __init__.py
    └── subsubpackage
        ├── __init__.py
        └── standalone.py

Esta vez standalone.py importará module.py del paquete de paquete utilizando la siguiente relativa de las importaciones

from ... import module  # N = 3

Tendremos que preceder esa línea con el código repetitivo para que funcione.

import sys
from pathlib import Path

if __name__ == '__main__' and __package__ is None:
    file = Path(__file__).resolve()
    parent, top = file.parent, file.parents[3]

    sys.path.append(str(top))
    try:
        sys.path.remove(str(parent))
    except ValueError: # Already removed
        pass

    import package.subpackage.subsubpackage
    __package__ = 'package.subpackage.subsubpackage'

from ... import module # N = 3

Nos permite ejecutar standalone.py por nombre de archivo:

vaultah@base:~$ python3 package/subpackage/subsubpackage/standalone.py
Running /home/vaultah/package/subpackage/subsubpackage/standalone.py
Importing /home/vaultah/package/__init__.py
Importing /home/vaultah/package/subpackage/__init__.py
Importing /home/vaultah/package/subpackage/subsubpackage/__init__.py
Importing /home/vaultah/package/module.py

Aquí se puede encontrar una solución más general envuelta en una función . Ejemplo de uso:

if __name__ == '__main__' and __package__ is None:
    import_parents(level=3) # N = 3

from ... import module
from ...module.submodule import thing

Solución n. ° 3: use importaciones absolutas y herramientas de configuración

Los pasos son:

  1. Reemplazar importaciones relativas explícitas con importaciones absolutas equivalentes

  2. Instalar packagepara hacerlo importable

Por ejemplo, la estructura del directorio puede ser la siguiente

.
├── project
│   ├── package
│   │   ├── __init__.py
│   │   ├── module.py
│   │   └── standalone.py
│   └── setup.py

donde setup.py es

from setuptools import setup, find_packages
setup(
    name = 'your_package_name',
    packages = find_packages(),
)

El resto de los archivos fueron tomados de la Solución # 1 .

La instalación le permitirá importar el paquete independientemente de su directorio de trabajo (suponiendo que no haya problemas de nombres).

Podemos modificar standalone.py para usar esta ventaja (paso 1):

from package import module  # absolute import

Cambie su directorio de trabajo projecty ejecútelo /path/to/python/interpreter setup.py install --user( --userinstala el paquete en el directorio de paquetes de su sitio ) (paso 2):

vaultah@base:~$ cd project
vaultah@base:~/project$ python3 setup.py install --user

Verifiquemos que ahora sea posible ejecutar standalone.py como script:

vaultah@base:~/project$ python3 -i package/standalone.py
Running /home/vaultah/project/package/standalone.py
Importing /home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/__init__.py
Importing /home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/module.py
>>> module
<module 'package.module' from '/home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/module.py'>
>>> import sys
>>> sys.modules['package']
<module 'package' from '/home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/__init__.py'>
>>> sys.modules['package.module']
<module 'package.module' from '/home/vaultah/.local/lib/python3.6/site-packages/your_package_name-0.0.0-py3.6.egg/package/module.py'>

Nota : Si decide seguir esta ruta, sería mejor usar entornos virtuales para instalar paquetes de forma aislada.

Solución # 4: Use importaciones absolutas y algo de código repetitivo

Francamente, la instalación no es necesaria: puede agregar un código repetitivo a su secuencia de comandos para que las importaciones absolutas funcionen.

Voy a tomar prestados archivos de la Solución n. ° 1 y cambiar standalone.py :

  1. Añadir el directorio padre del paquete a sys.path antes de intentar importar nada de paquete usando importaciones en términos absolutos:

    import sys
    from pathlib import Path # if you haven't already done so
    file = Path(__file__).resolve()
    parent, root = file.parent, file.parents[1]
    sys.path.append(str(root))
    
    # Additionally remove the current file's directory from sys.path
    try:
        sys.path.remove(str(parent))
    except ValueError: # Already removed
        pass
  2. Reemplace la importación relativa por la importación absoluta:

    from package import module  # absolute import

standalone.py se ejecuta sin problemas:

vaultah@base:~$ python3 -i package/standalone.py
Running /home/vaultah/package/standalone.py
Importing /home/vaultah/package/__init__.py
Importing /home/vaultah/package/module.py
>>> module
<module 'package.module' from '/home/vaultah/package/module.py'>
>>> import sys
>>> sys.modules['package']
<module 'package' from '/home/vaultah/package/__init__.py'>
>>> sys.modules['package.module']
<module 'package.module' from '/home/vaultah/package/module.py'>

Creo que debería advertirte: trata de no hacer esto, especialmente si tu proyecto tiene una estructura compleja.


Como nota al margen, PEP 8 recomienda el uso de importaciones absolutas, pero establece que en algunos escenarios las importaciones relativas explícitas son aceptables:

Se recomiendan las importaciones absolutas, ya que generalmente son más legibles y tienden a comportarse mejor (o al menos dan mejores mensajes de error). [...] Sin embargo, las importaciones relativas explícitas son una alternativa aceptable a las importaciones absolutas, especialmente cuando se trata de diseños de paquetes complejos donde el uso de importaciones absolutas sería innecesariamente detallado.


3
¿Es posible establecer __package__manualmente si el nombre es __main__para resolver el problema?
Paulo Scardine

Gracias, buenas respuestas! Pude cargar el módulo usando el impmódulo y configurarlo en __package__consecuencia, pero el resultado es claramente un antipatrón.
Paulo Scardine

Me sale el error AttributeError: 'PosixPath' object has no attribute 'path'.
usuario

Gracias por la pronta respuesta. Estoy usando el paquete nltk, obtengo un error: `File" /usr/local/lib/python3.5/dist-packages/nltk/__init__.py ", línea 115, en <module> de nltk.decorators decorador de importación, memorice el archivo "/usr/local/lib/python3.5/dist-packages/nltk/decorators.py", línea 23, en <module> sys.path = [p para p en sys.path if "nltk "no en p] Archivo" /usr/local/lib/python3.5/dist-packages/nltk/decorators.py ", línea 23, en <listcomp> sys.path = [p para p en sys.path if" nltk "no está en p] TypeError: el argumento de tipo 'PosixPath' no es iterable`
usuario

1
También puede importar un archivo por ruta de archivo (también relativa): docs.python.org/3/library/…
Ctrl-C

86

Ponga esto dentro del archivo __init__.py de su paquete :

# For relative imports to work in Python 3.6
import os, sys; sys.path.append(os.path.dirname(os.path.realpath(__file__)))

Asumiendo que su paquete es así:

├── project
   ├── package
      ├── __init__.py
      ├── module1.py
      └── module2.py
   └── setup.py

Ahora use importaciones regulares en su paquete, como:

# in module2.py
from module1 import class1

Esto funciona tanto en python 2 como en 3.


1
funciona esto si lo empaquetamos como weel?
Alex Punnen

1
También creo que esto merece más votos. Poner esto en todos __init__.pyresolverá básicamente todos los errores de importación relativos.
frankliuao

3
No puedo hablar por otros, pero tiendo a evitar la modificación sys.pathporque me preocupa que pueda afectar a otro código. (Esto se debe en parte a que no conozco las complejidades de cómo funciona)
PianoJames

@pianoJames Sé a lo que te refieres, esta solución mágica (aparentemente después de mucho jugar) parece demasiado fácil. Pero funciona. Sería interesante no saber de aquellos que saben si esto tiene efectos secundarios negativos.
Jon

Estoy usando esto ahora: y hasta ahora todo bien.
Java

37

Me encontré con este problema. Se está importando una solución alternativa de pirateo a través de un bloque if / else como el siguiente:

#!/usr/bin/env python3
#myothermodule

if __name__ == '__main__':
    from mymodule import as_int
else:
    from .mymodule import as_int


# Exported function
def add(a, b):
    return as_int(a) + as_int(b)

# Test function for module  
def _test():
    assert add('1', '1') == 2

if __name__ == '__main__':
    _test()

29
Esa no es una muy buena solución. Además, desnudo except:es malo. utilizar except ImportError:en su lugar!
ThiefMaster

66
Esta SystemErroraqui (Py 3.4)
Avi

8
Esta no es una idea terrible, pero sería mejor detectar qué importación usar en lugar de probar / excepto. Algo así como if __name__ == '__main__': from mymod import as_int; else: from .mymod import as_int.
Perkins

@Perkins Bueno ... en la mayoría de los casos no lo haría . Sin embargo, creo que las importaciones relativas pueden ser la excepción.
wizzwizz4

8

Con suerte, esto será de valor para alguien por ahí: revisé media docena de publicaciones de stackoverflow tratando de descubrir importaciones relativas similares a las publicadas aquí arriba. Configuré todo según lo sugerido pero todavía estaba golpeandoModuleNotFoundError: No module named 'my_module_name'

Como solo estaba desarrollando localmente y jugando, no había creado / ejecutado un setup.pyarchivo. Tampoco aparentemente había configurado mi PYTHONPATH.

Me di cuenta de que cuando ejecuté mi código como lo había estado cuando las pruebas estaban en el mismo directorio que el módulo, no pude encontrar mi módulo:

$ python3 test/my_module/module_test.py                                                                                                               2.4.0
Traceback (most recent call last):
  File "test/my_module/module_test.py", line 6, in <module>
    from my_module.module import *
ModuleNotFoundError: No module named 'my_module'

Sin embargo, cuando especifiqué explícitamente la ruta, las cosas comenzaron a funcionar:

$ PYTHONPATH=. python3 test/my_module/module_test.py                                                                                                  2.4.0
...........
----------------------------------------------------------------------
Ran 11 tests in 0.001s

OK

Entonces, en el caso de que alguien haya intentado algunas sugerencias, cree que su código está estructurado correctamente y aún se encuentra en una situación similar a la mía, intente cualquiera de los siguientes si no exporta el directorio actual a su PYTHONPATH:

  1. Ejecute su código e incluya explícitamente la ruta de la siguiente manera: $ PYTHONPATH=. python3 test/my_module/module_test.py
  2. Para evitar llamadas PYTHONPATH=., cree un setup.pyarchivo con contenido como el siguiente y ejecútelo python setup.py developmentpara agregar paquetes a la ruta:
# setup.py
from setuptools import setup, find_packages

setup(
    name='sample',
    packages=find_packages()
)

6

Necesitaba ejecutar python3 desde el directorio principal del proyecto para que funcione.

Por ejemplo, si el proyecto tiene la siguiente estructura:

project_demo/
├── main.py
├── some_package/
   ├── __init__.py
   └── project_configs.py
└── test/
    └── test_project_configs.py

Solución

Ejecutaría python3 dentro de la carpeta project_demo / y luego realizaría un

from some_package import project_configs

4

Para evitar este problema, ideé una solución con el paquete de reempaquetado , que me ha funcionado durante algún tiempo. Agrega el directorio superior a la ruta lib:

import repackage
repackage.up()
from mypackage.mymodule import myfunction

El reempaquetado puede realizar importaciones relativas que funcionan en una amplia gama de casos, utilizando una estrategia inteligente (inspección de la pila de llamadas).


¡Con mucho, la solución más fácil! ¡Gracias!
CodingInCircles

1
¡Gracias! En lugar de tratar de dar la mejor respuesta, traté de dar una respuesta útil :-)
fralau

3

si ambos paquetes están en su ruta de importación (sys.path), y el módulo / clase que desea es en example / example.py, para acceder a la clase sin importar relativa intente:

from example.example import fkt

1

Creo que la mejor solución es crear un paquete para su módulo: aquí hay más información sobre cómo hacerlo.

Una vez que tenga un paquete, no necesita preocuparse por la importación relativa, solo puede hacer importaciones absolutas.


0

Tuve un problema similar: necesitaba un servicio de Linux y un complemento cgi que usaran constantes comunes para cooperar. La forma 'natural' de hacer esto es colocarlos en el init archivo .py del paquete, pero no puedo iniciar el complemento cgi con el parámetro -m.

Mi solución final fue similar a la Solución # 2 anterior:

import sys
import pathlib as p
import importlib

pp = p.Path(sys.argv[0])
pack = pp.resolve().parent

pkg = importlib.import_module('__init__', package=str(pack))

La desventaja es que debe anteponer las constantes (o funciones comunes) con pkg:

print(pkg.Glob)
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.