verde una soñar significa serpientes que piton negra muerde gruesa grande gigante culebra con ataca amarilla python python-import runpy

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