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_ImportModuleLevelObject
funció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.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 de forma explícita antes de realizar la importación relativa .
Nota: El parche del problema 18018 ha agregado otro if
bloque , 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.py
fallará. Sin embargo, podemos ejecutar el módulo con la -m
opción de línea de comando que "buscará sys.path
el 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'>
-m
hace 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í,
Agregue el directorio padre del enésimo predecesor del módulo actual asys.path
Eliminar el directorio del archivo actual de sys.path
Importe el módulo principal del módulo actual utilizando su nombre completo
Establecer __package__
en el nombre completo de 2
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
Los pasos son:
Reemplazar importaciones relativas explícitas con importaciones absolutas equivalentes
Instalar 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 (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 project
y ejecútelo /path/to/python/interpreter setup.py install --user
( --user
instala 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 :
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
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.