2010-05-31 11 views
31

que tienen una "estructura de archivos canónica" como que (estoy dando nombres sensibles para facilitar la lectura):pesadilla con importaciones relativas, ¿cómo funciona pep 366?

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 lo desea, puede acceder a los servicios públicos helpers a través de las importaciones relativas en algo como:

# network/clientlib.py 
from ..helpers.path import create_dir 

el programa es ejecutado "como una secuencia de comandos" mediante el archivo __main__.py de esta manera:

python mainpack/ 

Tratando de seguir la PEP 366 He puesto en __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?

He intentado también agregar el directorio actual a PYTHONPATH, nada cambia.

Respuesta

7

El código de carga parece ser algo así 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) 

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? Es debería poder encontrarlo.

Lo he probado y he recibido el mismo error. Después de leer un poco me encontré con 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 a mí hacker pero funciona. El truco parece ser asegurarse de que el paquete foo esté cargado, por lo que la importación puede ser relativa.

+0

soy capaz de importar desde el directorio Estoy lanzando, no tengo que ver esto mejor – pygabriel

+0

@pygabriel ¿Es posible que usted pueda comprimir y ponerlo en la web en alguna parte? – extraneon

+0

seguro! http://dl.dropbox.com/u/1276730/mainpack.zip – pygabriel

41

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. extraneon la solución 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. El siguiente parece una mejor repetitivo de lo que se da en PEP 366 de asegurar que un módulo de Python puede ejecutarse independientemente de la forma en que se invoca (a través de un habitual import, o con python -m, o con python, en cualquier lugar):

# 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 el os.path.dirname debe repetirse hasta que parent_dir sea el directorio que contiene el nivel superior.

+6

Esto fue difícil de encontrar, ¡pero exactamente lo que estaba buscando! – knite

+1

¡Gracias, esto funciona perfectamente! En una nota al margen: se recomienda usar sys.path.insert (1, parent_dir) (en lugar de 0) si no es sys.path.append(), ver http://stackoverflow.com/questions/10095037/why-use -sys-path-appendpath-instead-of-sys-path-insert1-path – balu

+1

¿Podría mostrarnos la estructura del archivo? Además, ¿por qué 'str (" mypackage ")' en lugar de simplemente '" mypackage "'? – ArtOfWarfare

6

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 compilar el nombre completo del paquete. Esto definitivamente es raro, pero parece funcionar independientemente de la profundidad del archivo en su árbol de directorios. Parece importaciones absolutas son fuertemente alentado.

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 
+2

Esto funcionó para mí, excepto que cambié el primer \ _ \ _ paquete \ _ \ _ = [f] a \ _ \ _ paquete \ _ \ _ = []. Parece que la ruta relativa no funciona correctamente si el nombre del paquete se establece en el módulo que lo carga, sino que debe ser el paquete que contiene ese módulo. – KDN

+0

He estado buscando una solución para este problema durante mucho tiempo y funciona (con el parche de @KDN). Yo uso Python 2.7 con 'from __future__ import absolute_import'. – Jabba

0

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 ese. También tiene la ventaja de que se puede llamar el guión runme.py desde cualquier lugar y parece como que está haciendo lo correcto - Todavía no lo he probado en una configuración más compleja, por lo que caveat emptor ... etc

Esto 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 se parece a esto:

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