python - una - pesadilla con importaciones relativas, ¿cómo funciona pep 366?
soñar con serpientes (4)
El "modelo repetitivo" dado en PEP 366 parece incompleto. Aunque establece la variable __package__
, en realidad no importa el paquete, que también es necesario para permitir que las importaciones relativas funcionen. la solución de extraneon está en el camino correcto.
Tenga en cuenta que no es suficiente simplemente tener el directorio que contiene el módulo en sys.path
, el paquete correspondiente debe ser importado explícitamente. Lo siguiente parece una mejor repetición de lo que se dio en PEP 366 para garantizar que un módulo de python pueda ejecutarse independientemente de cómo se invoque (a través de una import
regular, o con python -m
, o con python
, desde cualquier ubicación):
# boilerplate to allow running as script directly
if __name__ == "__main__" and __package__ is None:
import sys, os
# The following assumes the script is in the top level of the package
# directory. We use dirname() to help get the parent directory to add to
# sys.path, so that we can import the current package. This is necessary
# since when invoked directly, the ''current'' package is not automatically
# imported.
parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(1, parent_dir)
import mypackage
__package__ = str("mypackage")
del sys, os
# now you can use relative imports here that will work regardless of how this
# python file was accessed (either through ''import'', through ''python -m'', or
# directly.
Si la secuencia de comandos no está en el nivel superior del directorio del paquete y necesita importar un módulo por debajo del nivel superior, entonces se debe repetir el parent_dir
os.path.dirname
hasta que parent_dir
sea el directorio que contiene el nivel superior.
Tengo una "estructura de archivos canónicos" como esa (le doy nombres razonables para facilitar la lectura):
mainpack/
__main__.py
__init__.py
- helpers/
__init__.py
path.py
- network/
__init__.py
clientlib.py
server.py
- gui/
__init__.py
mainwindow.py
controllers.py
En esta estructura, por ejemplo, los módulos contenidos en cada paquete pueden querer acceder a las utilidades helpers
través de importaciones relativas en algo como:
# network/clientlib.py
from ..helpers.path import create_dir
El programa se ejecuta "como una secuencia de comandos" utilizando el archivo __main__.py
de esta manera:
python mainpack/
Tratando de seguir el PEP 366 He puesto __main__.py
estas líneas:
___package___ = "mainpack"
from .network.clientlib import helloclient
Pero cuando se ejecuta:
$ python mainpack
Traceback (most recent call last):
File "/usr/lib/python2.6/runpy.py", line 122, in _run_module_as_main
"__main__", fname, loader, pkg_name)
File "/usr/lib/python2.6/runpy.py", line 34, in _run_code
exec code in run_globals
File "path/mainpack/__main__.py", line 2, in <module>
from .network.clientlib import helloclient
SystemError: Parent module ''mainpack'' not loaded, cannot perform relative import
¿Qué pasa? ¿Cuál es la forma correcta de manejar y usar efectivamente las importaciones relativas?
También intenté agregar el directorio actual a PYTHONPATH, nada cambia.
El código de carga parece ser algo como this :
try:
return sys.modules[pkgname]
except KeyError:
if level < 1:
warn("Parent module ''%s'' not found while handling "
"absolute import" % pkgname, RuntimeWarning, 1)
return None
else:
raise SystemError, ("Parent module ''%s'' not loaded, cannot "
"perform relative import" % pkgname)
lo que me hace pensar que tal vez su módulo no está en sys.path. Si inicias Python (normalmente) y simplemente escribes "import mainpack" en el prompt, ¿qué hace? Debería ser capaz de encontrarlo.
Lo intenté yo mismo y obtuve el mismo error. Después de leer un poco encontré la siguiente solución:
# foo/__main__.py
import sys
mod = __import__(''foo'')
sys.modules["foo"]=mod
__package__=''foo''
from .bar import hello
hello()
Me parece un poco hackish pero funciona. El truco parece ser asegurarse de que el paquete foo
se cargue para que la importación pueda ser relativa.
Esta es una configuración mínima basada en la mayoría de las otras respuestas, probada en Python 2.7 con un diseño de paquete como tal. También tiene la ventaja de que puede invocar el script runme.py
desde cualquier lugar y parece que está haciendo lo correcto: aún no lo he probado en una configuración más compleja, por lo que se requiere una advertencia, etc.
Esta es básicamente la respuesta de Brad anterior con la inserción en sys.path que otros han descrito.
packagetest/
__init__.py # Empty
mylib/
__init__.py # Empty
utils.py # def times2(x): return x*2
scripts/
__init__.py # Empty
runme.py # See below (executable)
runme.py
ve así:
#!/usr/bin/env python
if __name__ == ''__main__'' and __package__ is None:
from os import sys, path
d = path.dirname(path.abspath(__file__))
__package__ = []
while path.exists(path.join(d, ''__init__.py'')):
d, name = path.split(d)
__package__.append(name)
__package__ = ".".join(reversed(__package__))
sys.path.insert(1, d)
mod = __import__(__package__)
sys.modules[__package__] = mod
from ..mylib.utils import times2
print times2(4)
Inspirado por las respuestas de extraneon y taherh aquí hay un código que se ejecuta en el árbol de archivos hasta que se agote de los archivos __init__.py
para generar el nombre completo del paquete. Esto definitivamente es raro, pero parece funcionar independientemente de la profundidad del archivo en su árbol de directorios. Parece que las importaciones absolutas son fuertemente alentadas.
import os, sys
if __name__ == "__main__" and __package__ is None:
d,f = os.path.split(os.path.abspath(__file__))
f = os.path.splitext(f)[0]
__package__ = [f] #__package__ will be a reversed list of package name parts
while os.path.exists(os.path.join(d,''__init__.py'')): #go up until we run out of __init__.py files
d,name = os.path.split(d) #pull of a lowest level directory name
__package__.append(name) #add it to the package parts list
__package__ = ".".join(reversed(__package__)) #create the full package name
mod = __import__(__package__) #this assumes the top level package is in your $PYTHONPATH
sys.modules[__package__] = mod #add to modules