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í,
Agregue el directorio principal del Nth predecesor del módulo actual a
sys.path
Eliminar el directorio del archivo actual de
sys.path
Importe el módulo principal del módulo actual con su nombre completo
Establezca
__package__
en el nombre completo de 2Realizar 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:
Reemplazar importaciones relativas explícitas con importaciones absolutas equivalentes
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 :
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
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:
- 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
- Para evitar llamar a
PYTHONPATH=.
, cree un archivosetup.py
con contenidos como los siguientes y ejecute elpython 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.