[Python] 也整理一下萬惡的Python import機制
關於Python import,基本上是個寫Python的人都遇到過(除非你從來不拆分專案的目錄結構)。這個問題在StackOverflow上被討論了七八年,但其實早就有人提出過,只要模組搜尋路徑裡包含專案根目錄,這個愚蠢的問題就沒了,但從來沒有人正面回答過這個問題。
結果就是,萬惡的import機制仍然在禍害著每一個人。
Python的import機制坑在哪?
假設專案目錄是這樣子的——
import_test
|_______ modules
| |____ __init__。py
| |____ a。py
| |____ b。py
|_______ tests
| |____ __init__。py
| |____ test_a。py
| |____ test_b。py
|_______ app。py
app。py
假設是你的主程式,從這裡執行的時候,
import
是不太容易出問題的,絕對路徑也好相對路徑也好。坑在
tests
這個目錄,把測試指令碼放到一個目錄裡這本身沒問題
,但是,如果你在tests下直接執行這些測試指令碼,你就會踩坑。
比如,
test_a。py
從
modules
裡匯入了
a
,如下所示——
from modules import a
a = a。a1()
if a > 50:
print(a)
else:
print(100-a)
當你執行的時候,你會發現
Python
找不到
modules
在哪 ,這是自然的,因為模組搜尋路徑裡沒有包含上級目錄,所以會報錯
ModuleNotFoundError: No module named ‘modules’
import_test> python tests/test_a。py
Traceback (most recent call last):
File “tests/test_a。py”, line 1, in
from modules import a
ModuleNotFoundError: No module named ‘modules’
import_test>
有人說
-m
這個引數有效,我試過,
仍然是要退回到import_test這個目錄,
而且要注意,
不能加.py,因為此時你執行的是一個模組
,如下所示——
import_test> python -m tests。test_a
100
import_test>
Pycharm的最佳化可能會讓你更懵逼
其次,如果你用pycharm,你
可能會直到上線的那天
你才發現這個問題,因為pycharm非常人性化,它
自動幫你把專案路徑新增
好了,所以在pycharm裡,你是可以直接在
tests
下 執行
test_a。py
的 ,不會有任何問題。差別很容易找到,在程式碼最前面加上這麼兩行,
然後分別在pycharm和其它IDE,比如vscode裡執行——
import
sys
(
sys
。
path
)
你會發現
pycharm裡打出來的路徑是更多的,它把專案根目錄加進去了。
如果你在不知情的情況下繼續開發下去,並且你把需要直接執行的程式碼,放在了類似於
tests
這樣的目錄下面,而不是
import_test
這個根目錄下,等你上線程式碼那天,你就有的哭了,你會看到各種匯入失敗的錯誤,而你平時用pycharm卻毫無感覺……
也許這才是真正科學的做法——把測試case寫成模組,而不是指令碼
我因為最初用的vscode,在import這個問題上糾結了無數次……然而python的import就是這麼反人類,我又不想把測試指令碼全都扔到最外面,後來我發現很多專案是把測試的case寫在
tests
目錄下,然後
在外面留一個入口指令碼去匯入這些測試case,說白了,測試case本身也變成了一個模組,測試目錄變成了一個包。
參考
最後貼兩篇有用的參考,第二篇是一位臺灣老師寫的
Python專案如何合理組織規避import天坑
Python的Import陷阱
我摘出其中最關鍵的解釋
# 標準的 explicit relative import 寫法
from 。sample_module import sample_func
1。 包含相對路徑的檔案不能直接執行,只能作為 module 被引用,所以失敗
2。 成功印出 Hello!
相對引用是有限制的,比如
tests
目錄下的模組是沒辦法透過相對引用,直接引用到
modules
下面去的
以我們的例子來看
from 。a import a1
a = a1()
if a > 50:
print(a)
else:
print(100-a)
這個執行的結果將會是一個錯誤
import_test> python -m tests。test_a
Traceback (most recent call last):
……
ModuleNotFoundError: No module named ‘tests。a’
import_test>
所以,我個人跨包匯入的時候,都是用的絕對匯入
# 標準的 absolute import 寫法
from sample_package。sample_module import sample_func
1。 如果此層目錄位置不在 python path 中,就會失敗
2。 成功印出 Hello!
也就是
from modules import a
a = a。a1()
if a > 50:
print(a)
else:
print(100-a)
小結
貼一個stackoverflow的連線,在這個連線裡,被採納的回答在7年後更新了答案,所以你就知道這個問題真的是個老問題了……
https://
stackoverflow。com/quest
ions/6323860/sibling-package-imports/50193944#50193944
一個更加廣為人知的做法是sys。path。append,但就像很多在stackoverflow上提問的人說的那樣,我個人一點也不喜歡這種做法,也許只是用在測試腳本里沒有問題,但它看上去更奇怪
另外,這個連結裡也有一段有意思的內容——
換言之,Python的開發團隊是知道這個問題也討論過的,所以,真傲慢。
The only use case?