2010-05-31 29 views
31

我有一個「規範的文件結構」這樣的(我給懂事的名字來緩解讀):相關進口噩夢,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 

在這種結構中,包含在每個包例如模塊可能要訪問的helpers公用事業通過在一些相對進口,如:

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

該計劃是拼命地跑「爲腳本」使用__main__.py文件中這樣說:

python mainpack/ 

試圖跟隨PEP 366我已經把__main__.py這些行:

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

但運行時:

$ 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 

有什麼不對?處理和有效使用相關進口的正確方法是什麼?

我也嘗試將當前目錄添加到PYTHONPATH,沒有任何更改。

回答

7

加載代碼似乎是這樣的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) 

這讓我覺得,也許你的模塊是不是在sys.path中。如果你啓動Python(通常),並在提示符處輸入「import mainpack」,它會做什麼?它應該能夠找到它。

我自己試過了,得到了同樣的錯誤。讀了一下後,我發現了以下解決方案:

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

__package__='foo' 
from .bar import hello 

hello() 

這似乎有點hackish給我,但它確實工作。訣竅似乎是確保包foo已加載,因此導入可以是相對的。

+0

我能夠從我啓動目錄導入它,我已經看這個更好 – pygabriel 2010-05-31 15:54:44

+0

@pygabriel是否有可能爲你壓縮它並把它放在網上的某個地方?當然是 – extraneon 2010-06-01 07:09:45

+0

! http://dl.dropbox.com/u/1276730/mainpack.zip – pygabriel 2010-06-01 07:41:25

41

PEP 366中給出的「樣板」似乎不完整。儘管它設置了__package__變量,但它並未實際導入包,這也是允許相關導入工作所需的包。 extraneon的解決方案是在正確的軌道上。

請注意,只有包含sys.path模塊的目錄是不夠的,相應的包需要明確導入。下面好像不是什麼PEP 366給出確保一個Python模塊可以不考慮執行它是如何調用(通過定期import,或python -m,或者python,在任何地點)更好的樣板:

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

如果腳本不在軟件包目錄的頂層,並且您需要將模塊導入頂層以下,則必須重複os.path.dirname,直到parent_dir是包含頂層的目錄。

+6

這很難找到,但正是我所期待的! – knite 2013-07-18 05:49:58

+1

謝謝,這個作品完美!注意:建議使用sys.path.insert(1,parent_dir)(而不是0),如果不是sys.path.append(),請參閱http://stackoverflow.com/questions/10095037/why-use -sys-path-appendpath-instead-of-sys-path-insert1-path – balu 2013-08-13 20:31:37

+1

你能告訴我們文件結構嗎?另外,爲什麼'str(「mypackage」)'而不是''mypackage「'? – ArtOfWarfare 2015-03-30 20:27:44

6

受到extraneon和taherh的回答的啓發,這裏有一些代碼運行文件樹,直到用完__init__.py文件來構建完整軟件包名稱。這絕對是不好的,但無論目錄樹中文件的深度如何,它似乎都可以工作。看來絕對進口是沉重鼓勵。

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

這對我來說很有效,除了我將第一個\ _ \ _ package \ _ \ _ = [f]更改爲\ _ \ _ package \ _ \ _ = []。看起來,如果包名被設置爲加載它的模塊,則相對路徑無法正常工作 - 相反,它必須是包含該模塊的包。 – KDN 2013-07-18 17:54:13

+0

我一直在尋找這個問題很長一段時間的解決方案,它的工作原理(使用@KDN補丁)。我使用Python 2.7和'from __future__ import absolute_import'。 – Jabba 2014-01-02 17:50:12

0

這是基於最其他的答案,與像這樣的封裝佈局有關python 2.7測試的最小設置。它還具有的優點是你可以從任何地方撥打runme.py腳本,它似乎像它做正確的事 - 我還沒有在一個更復雜的設置進行測試,所以買者自負......等

這基本上是Brad的回答,其中插入到sys.path中的其他人已經描述過。

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看起來是這樣的:

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