2011-06-12 147 views
55

我已經嘗試閱讀有關兄弟姐妹進口甚至 package documentation的問題,但我還沒有找到答案。兄弟姐妹包進口

結構如下:

├── LICENSE.md 
├── README.md 
├── api 
│   ├── __init__.py 
│   ├── api.py 
│   └── api_key.py 
├── examples 
│   ├── __init__.py 
│   ├── example_one.py 
│   └── example_two.py 
└── tests 
│   ├── __init__.py 
│   └── test_one.py 

如何從 api模塊examplestests目錄導入腳本並從命令行運行?

此外,我想避免醜陋的sys.path.insert破解每個文件。當然 這可以在Python中完成,對吧?

回答

26

如在別處已經說過的,可怕的事實是,你必須做的醜陋黑客以允許從__main__模塊從兄弟姐妹模塊的進口或父母包。該問題詳述於PEP 366PEP 3122試圖處理進口更合理的方式,但圭多已經拒絕了一個賬戶的

The only use case seems to be running scripts that happen to be living inside a module's directory, which I've always seen as an antipattern.

here

雖然,我使用定期這種模式與

# Ugly hack to allow absolute import from the root folder 
# whatever its name is. Please forgive the heresy. 
if __name__ == "__main__" and __package__ is None: 
    from sys import path 
    from os.path import dirname as dir 

    path.append(dir(path[0])) 
    __package__ = "examples" 

import api 

這裏path[0]是您的運行腳本的父文件夾和dir(path[0])您的頂級文件夾。

我仍然沒有能夠使用相對導入這個問題,雖然,但它允許從頂層絕對進口(在你的榜樣api的父文件夾)。

+3

也適用於path.append('..') – halflings 2014-01-31 09:32:35

+2

或者如果你安裝了軟件包](http://stackoverflow.com/a/23542795/4279)(pip和virtualenv使其變得簡單) – jfs 2014-05-08 13:15:35

1

您需要查看導入語句是如何寫入相關代碼的。如果examples/example_one.py使用以下import語句:

import api.api 

...那麼,它預計該項目的根目錄是在系統路徑。

來支持這一點沒有任何黑客(因爲你把它),可運行從頂級目錄的例子,比如最簡單的方法:

PYTHONPATH=$PYTHONPATH:. python examples/example_one.py 
+0

使用Python 2.7.1,我得到如下:'$蟒蛇例子/ example.py 回溯(REC ent call last): 來自api.api的文件「examples/example.py」,第3行, import API ImportError:沒有名爲api.api的模塊。我也使用'import api.api'獲得相同的結果。 – zachwill 2011-06-12 18:51:03

+0

更新了我的答案......你**必須將當前目錄添加到導入路徑,沒有辦法。 – 2011-06-12 18:58:03

0

首先,你應該避免與文件與模塊本身同名。它可能會打破其他進口。

當您導入文件時,首先解釋器檢查當前目錄,然後搜索全局目錄。

examplestests您可以撥打:

from ..api import api 
+0

我使用Python 2.7.1獲得以下內容:'Traceback(最近調用最後一次): 文件「example_one。py「,第3行,在 from ..api import api ValueError:試圖在非包中相對導入 – zachwill 2011-06-12 20:35:21

+2

呵呵,那麼你應該在頂層目錄中添加一個__init __。py'文件,否則Python可能會'把它當作一個模塊 – 2011-06-13 00:03:49

+5

它不會工作問題不在於父文件夾不是包,而是因爲模塊的__name__是__main__而不是'package.module',Python可以'如果你[使用'-m'形式從一個項目目錄運行,那麼'.'指向什麼也沒有。 – Evpok 2011-06-24 10:00:52

30

這裏是我在Python文件的頂部tests文件夾中插入另一選擇:

# Path hack. 
import sys, os 
sys.path.insert(0, os.path.abspath('..')) 
+1

+1真的很簡單,它的工作非常完美,您需要將父類添加到導入(例如api.api,例子s.example_two),但我更喜歡這種方式。 – 2012-06-01 21:35:39

8

我還沒有Pythonology所必需的理解看到的共享代碼之間不相關的項目預期的方式沒有兄弟姐妹/相對進口黑客攻擊。直到那一天,這是我的解決方案。對於examplestests..\api進口的東西,它看起來像:

import sys.path 
import os.path 
# Import from sibling directory ..\api 
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/..") 
import api.api 
import api.api_key 
+0

這仍然會給你api父目錄,你不需要「/ ..」連接sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__ file__)) )) – 2014-07-23 03:46:33

1

萬一有人使用Eclipse的Pydev的結束了在這裏:你可以爲添加的兄弟姐妹的父路徑(因而調用模塊的母公司)外部庫文件夾使用項目 - >屬性和設置外部庫在左側菜單Pydev-PYTHONPATH。然後你可以從你的兄弟姐妹導入, G。 from sibling import some_class

11

你不需要也不應該破解sys.path除非有必要,在這種情況下,它不是。使用:

import api.api_key # in tests, examples 

從項目目錄運行:python -m tests.test_one

你或許應該移動tests(如果他們是API的單元測試)內api和運行python -m api.test運行所有測試(假設有__main__.py)或python -m api.test.test_one運行test_one代替。

你也可以刪除examples__init__.py(它不是一個Python包),並在api是例如安裝了virtualenv中運行示例,pip install -e .在virtualenv中,如果你有正確的setup.py將安裝就位api包。

+1

如果測試不是api的單元測試,該怎麼辦? – Alex 2016-11-24 19:38:40

+0

@Alex答案並不假定測試是API測試*,除了*表示明確說明的段落*「如果它們是api的unittests」*。 – jfs 2016-11-24 20:30:30

+0

不幸的是,你堅持從根目錄運行和PyCharm仍然沒有找到該文件的好功能 – mhstnsc 2017-11-03 17:28:13

2

對於兄弟姐妹包導入,可以使用任一插入追加的方法[sys.path中] [2]模塊:

if __name__ == '__main__' and if __package__ is None: 
    import sys 
    from os import path 
    sys.path.append(path.dirname(path.dirname(path.abspath(__file__)))) 
    import api 

如果你是這將工作啓動您的腳本如下:

python examples/example_one.py 
python tests/test_one.py 

在另一方面,你也可以用相對進口:

if __name__ == '__main__' and if __package__ is not None: 
    import ..api.api 

在這種情況下,你將不得不與'-m' argument啓動腳本(注意,在這種情況下,你一定不要給「的.py」擴展名):

python -m packageName.examples.example_one 
python -m packageName.tests.test_one 

當然,可以混合使用這兩種方法,讓你的腳本將工作無論怎麼叫:

if __name__ == '__main__': 
    if __package__ is None: 
     import sys 
     from os import path 
     sys.path.append(path.dirname(path.dirname(path.abspath(__file__)))) 
     import api 
    else: 
     import ..api.api