2010-05-31 14 views
31

Ho un "struttura di file canonica" come quello (sto dando nomi sensati per facilitare la lettura):incubo con importazioni relative, come funziona 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 

In questa struttura, ad esempio moduli contenuti in ogni pacchetto può essere utile per accedere alle utility helpers attraverso le importazioni relative a qualcosa di simile:

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

il programma è gestito "come uno script" utilizzando il file __main__.py in questo modo:

python mainpack/ 

Cercando di seguire il PEP 366 ho messo in __main__.py queste righe:

___package___ = "mainpack" 
from .network.clientlib import helloclient 

Ma durante l'esecuzione:

$ 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 

Cosa c'è di sbagliato? Qual è il modo corretto di gestire e utilizzare efficacemente le importazioni relative?

Ho provato anche ad aggiungere la directory corrente al PYTHONPATH, non cambia nulla.

risposta

7

Il codice caricamento sembra essere qualcosa di simile a 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) 

che mi fa pensare che forse il modulo non è in sys.path. Se avvii Python (normalmente) e scrivi semplicemente "import mainpack" sul prompt, che cosa fa? E 'dovrebbe essere in grado di trovarlo.

Ho provato da solo e ho ottenuto lo stesso errore. Dopo aver letto un po 'ho trovato la seguente soluzione:

# foo/__main__.py 
import sys 
mod = __import__('foo') 
sys.modules["foo"]=mod 

__package__='foo' 
from .bar import hello 

hello() 

Mi sembra un po' hackerato ma funziona. Il trucco sembra essere assicurandosi che il pacchetto foo sia caricato in modo che l'importazione possa essere relativa.

+0

sono in grado di importare dalla directory Sto lanciando, ho a guardare questo meglio – pygabriel

+0

@pygabriel E 'possibile per voi per zip e lo metti sul web da qualche parte? – extraneon

+0

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

41

Il "boilerplate" indicato in PEP 366 sembra incompleto. Sebbene imposti la variabile __package__, in realtà non importa il pacchetto, che è anche necessario per consentire il funzionamento delle importazioni relative. extraneon la soluzione è sulla strada giusta.

Si noti che non è sufficiente avere semplicemente la directory contenente il modulo in sys.path, il pacchetto corrispondente deve essere importato in modo esplicito. Quanto segue sembra un testo standard migliore di quella proposta nel PEP 366 per garantire che un modulo pitone può essere eseguita indipendentemente da come esso viene richiamato (attraverso un regolare import, o con python -m, o con python, da qualsiasi posizione):

# 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. 

Se lo script non si trova al livello più alto della directory del pacchetto ed è necessario importare un modulo sotto il livello superiore, è necessario ripetere lo os.path.dirname fino a quando lo parent_dir è la directory che contiene il livello superiore.

+6

Questo è stato difficile da trovare, ma esattamente quello che stavo cercando! – knite

+1

Grazie, funziona perfettamente! Nota a margine: si consiglia di utilizzare sys.path.insert (1, parent_dir) (anziché 0) se non sys.path.append(), consultare http://stackoverflow.com/questions/10095037/why-use -sys-path-appendpath-instead-of-sys-path-insert1-path – balu

+1

Puoi mostrarci la struttura del file? Inoltre, perché 'str (" mypackage ")' invece di solo '" mypackage "'? – ArtOfWarfare

6

Ispirato alle risposte di extraneon e taherh, ecco un codice che esegue l'albero dei file finché non esaurisce i file __init__.py per creare il nome completo del pacchetto. Questo è decisamente hacky, ma sembra funzionare indipendentemente dalla profondità del file nella struttura delle directory. Sembra che le importazioni assolute siano fortemente incoraggiate.

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

Questo ha funzionato per me, tranne che cambio il primo \ _ \ _ pacchetto \ _ \ _ = [f] in \ _ \ _ pacchetto \ _ \ _ = []. Sembra che il path relativo non funzioni correttamente se il nome del pacchetto è impostato sul modulo che lo carica - piuttosto, deve essere il pacchetto che contiene quel modulo. – KDN

+0

Ho cercato una soluzione per questo problema da molto tempo e funziona (con la patch di @KDN). Io uso Python 2.7 con 'from __future__ import absolute_import'. – Jabba

0

Si tratta di una configurazione minima basata sulla maggior parte delle altre risposte, testati su python 2.7 con un layout del pacchetto in questo modo. Essa ha anche il vantaggio che si può chiamare lo script runme.py da qualsiasi luogo e sembra come sta facendo la cosa giusta - non ho ancora testato in una configurazione più complessa, caveat emptor così ... ecc

Questa è fondamentalmente la risposta di Brad sopra con l'inserto in sys.path che altri hanno descritto.

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 assomiglia a questo:

#!/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) 
Problemi correlati