xticks barplot python python-3.x python-import

barplot - Importaciones relativas en Python 3



pandas plot (8)

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?

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

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

... con un mymodule.py como este ...

#!/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()

... un myothermodule.py como este ...

#!/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 un main.py como este ...

#!/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.py o mypackage/mymodule.py , pero falla con mypackage/myothermodule.py , debido a la importación relativa ...

from .mymodule import as_int

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

python3 -m mypackage.myothermodule

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

La solución más simple para este caso, asumiendo que el nombre mymodule es globalmente único, sería evitar el uso de importaciones relativas, y solo usar ...

from mymodule import as_int

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

from mypackage.mymodule import as_int

... o si desea que funcione "fuera de la caja", puede frobar el código PYTHONPATH en primer lugar con este ...

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 molesto, pero hay una pista de por qué en un correo electrónico escrito por un tal Guido van Rossum ...

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

Si ejecutar scripts dentro de un paquete es antipattern o no es subjetivo, pero personalmente lo encuentro realmente útil en un paquete que tengo que contiene algunos widgets wxPython personalizados, así que puedo ejecutar el script para que cualquiera de los archivos de origen muestre un wx.Frame contiene sólo ese widget para fines de prueba.

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

A veces me funciona con from .mymodule import myfunction pero 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?


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 de 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 el módulo en el sistema de archivos.

En algún punto, la PEP 338 entró en conflicto con la 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__ es siempre ''__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 con el modificador -m . Una pequeña cantidad de repetitivo en el propio módulo permitirá que las importaciones relativas funcionen cuando el archivo se ejecuta por nombre. [...] Cuando [el atributo] está presente, las importaciones relativas se basarán en este atributo en lugar del atributo del módulo __name__ . [...] Cuando el módulo principal se especifica por su nombre de archivo, entonces el atributo __package__ se establecerá en Ninguno . [...] Cuando el sistema de importación encuentra una importación relativa explícita en un módulo sin __package__ 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. Esta es la razón por la cual hay un literal de cadena vacía en la descripción del error:

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

La parte relevante de la función PyImport_ImportModuleLevelObject del 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 el package (el nombre del paquete) en interp->modules (accesible como sys.modules ). Dado que sys.modules es "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 explícitamente de manera absoluta antes de realizar una importación relativa .

Nota: El parche del problema 18018 ha agregado otro bloque if , 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 el 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ás esto en Python 3.6 o más reciente.

Solución # 1: Ejecute su script usando -m

Considere un directorio (que es un package 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 completamente, 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.py fallará. Sin embargo, podemos ejecutar el módulo con la opción de línea de comando -m que "buscará sys.path para el módulo nombrado y ejecutará su contenido como el módulo __main__ " :

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''>

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

Solución # 2: Establecer __package__ manualmente

Considérelo 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á incompleta, porque la configuración de __package__ solo no es suficiente. Necesitará importar al menos N paquetes anteriores en la jerarquía de módulos, donde N es el número de directorios principales (en relación con el directorio del script) en el que se buscará el módulo que se está importando.

Así,

  1. Agregue el directorio principal del Nth predecesor del módulo actual a sys.path

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

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

  4. Establezca __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 usando la siguiente importación relativa

from ... import module # N = 3

Tendremos que preceder esa línea con el código del boilerplate, 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

Una solución más general envuelta en una función se puede encontrar here . 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 # 3: usar importaciones absolutas y setuptools

Los pasos son:

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

  2. Instala el package para 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 (asumiendo que no habrá problemas de denominación).

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

from package import module # absolute import

Cambie su directorio de trabajo para project y ejecutar /path/to/python/interpreter setup.py install --user ( --user instala el paquete en su directorio de paquetes de sitio ) (paso 2):

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

Verifiquemos que ahora es posible ejecutar standalone.py como un 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 utilizar entornos virtuales para instalar paquetes de forma aislada.

Solución # 4: Utilice importaciones absolutas y algunos códigos repetitivos

Francamente, la instalación no es necesaria; puede agregar un código de repetición a su script para hacer que las importaciones absolutas funcionen.

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

  1. Agregue el directorio principal del paquete a sys.path antes de intentar importar algo del paquete usando importaciones absolutas:

    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''>

Siento que debo advertirle: trate de no hacer esto, especialmente si su proyecto tiene una estructura compleja.

Como nota adicional , PEP 8 recomienda el uso de importaciones absolutas, pero afirma 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.


Con suerte, esto será de gran valor para alguien que se encuentre allí: pasé por media docena de publicaciones de que intentaban averiguar las importaciones relativas similares a las que se publicaron aquí. Configuré todo como se sugirió, pero aún estaba ModuleNotFoundError: No module named ''my_module_name''

Desde que estaba desarrollando localmente y jugando, no había creado / ejecutado un archivo setup.py . Tampoco aparentemente había puesto mi PYTHONPATH .

Me di cuenta de que cuando ejecutaba mi código como lo estaba 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 especificé explícitamente la ruta, las cosas empezaron a funcionar:

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

Entonces, en 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 las siguientes opciones si no exporta el directorio actual a su PYTHONPATH:

  1. Ejecute su código e incluya explícitamente la ruta como: $ PYTHONPATH=. python3 test/my_module/module_test.py $ PYTHONPATH=. python3 test/my_module/module_test.py
  2. Para evitar llamar a PYTHONPATH=. , cree un archivo setup.py con contenidos como los siguientes y ejecute el python setup.py development para agregar paquetes a la ruta:

# setup.py from setuptools import setup, find_packages setup( name=''sample'', packages=find_packages() )


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

#!/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()


Para evitar este problema, he ideado una solución con el paquete de repackage , que me ha funcionado durante un tiempo. Agrega el directorio superior a la ruta lib:

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

El reenvasado puede realizar importaciones relativas que funcionan en una amplia gama de casos, utilizando una estrategia inteligente (inspeccionar la pila de llamadas).


Para mí, necesitaba ejecutar python3 desde el directorio principal para que funcione. Por ejemplo, si el proyecto tiene la siguiente estructura:

proyecto_demo

| - main.py

| - - algún_paquete

| - - - - __ init __ .py

| - - - - project_configs.py

| - - prueba

| - - - - test_project_configs.py

Solución: Ejecutaría python3 dentro de project_demo y luego desde some_package import project_configs .


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

from example.example import fkt


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__)))

Suponiendo que su paquete es así:

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

Ahora usa importaciones regulares en tu paquete, como:

# in module2.py from module1 import class1

Esto funciona tanto en python 2 como en 3.