您當前的位置:首頁 > 文化

Python 中 import機制的一些問題

作者:由 某鼠 發表于 文化時間:2019-08-30

sys。path 和 Working directory 是不同的

1. Working Directory 工作目錄

working directory

是在程式中

透過相對路徑訪問檔案的起始點

,是作業系統的概念,所有程式都會涉及到,在python中你可以透過下面的程式碼得到:

os

getcwd

()

# get current working directory

它可能會影響你開啟、儲存檔案, 只要不在程式中修改,在哪裡開啟的程式,

工作目錄(working directory)

就在哪裡。

舉個例子:

對於同一個檔案xxx。py, 分別從它所在目錄執行和在上一級目錄執行,

工作目錄

不相同:

$ python xxx。py

# 在xxx。py 所在目錄執行

$

cd

。。

$ python project/xxx。py

# 在xxx。py 上一級,目錄執行

這裡

python xxx。py

python project/xxx。py

雖然執行的是同一個目錄下同一個程式,但是

工作目錄

卻是不一樣的,一個在

xxx。py

所在目錄,另一個在上一級目錄。

這和c語言類似:

。/a。out

。/project/a。out

工作目錄

是不一樣的。

我們在程式裡面透過相對路徑訪問一個資源,都是依賴於這個執行時才確定的

工作目錄

的。需要注意的是這是一個執行時的變數,它是整個程式全域性共享的,不受程式碼檔案在哪個子目錄的影響。

初學者可能會誤以為

project/module1/a。py

中寫的程式碼與

project/module2/a。py

中寫的程式碼

工作目錄不同

,分別呼叫

open(‘xx。txt’)

會開啟各自目錄下的

xx。txt

但這是錯誤的看法

,在程式執行後他們會

共享一個工作目錄

,開啟

同一個檔案

,而這個檔案的具體位置由執行時呼叫的路徑確定。

2. 不是環境變數的PATH:sys.path

sys.path

也是程式執行時所有模組共享的, 它表示是import 查詢的路徑, 你可能會認為

sys.path

working directory

是一樣的,但其實不是,

sys.path

是由開始執行的檔案(入口檔案)位置決定的

python xxx。py

python project/xxx。py

工作目錄

不同,但是

sys.path

卻相同,都是

xxx。py

所在的位置。這樣的機制保證了import 不受執行路徑的影響, 是十分合理的設計。

但是,有些時候,我們會看到有人喜歡把這個開始執行的入口檔案放在子目錄中 (雖然我們並不建議這樣做,但是在程式debug的時候,我們希望單獨執行一個子目錄中的檔案,這種情況還是時有發生)

例如這個入口檔案在這裡:

task/main。py

當我們執行

# 目錄結構示意圖

# project/

# ├── task

# │ ├── main。py

# │ └── 。。。。

# ├── util

# │ ├── xx_util。py

# │ └── 。。。。

# └── 。。。。

$ python task/main。py

sys.path

就會進入到task 目錄中,這樣main。py 想要 import main 目錄外的模組就會出問題,例如

import util。xx_util

就會出現

ModuleNotFoundError

, 就算本目錄下的檔案

import task。xxx

也會出現錯誤,因為

sys.path

不對,在task目錄下就沒有那些檔案。

這種情況怎麼辦呢?我看到過幾種做法:

sys。path。append(‘。。’)

將上一級目錄 append 進來

非常不推薦這種做法,動態改變

sys.path

會使靜態分析工具失效,例如pycharm 等IDE的程式碼提示。這在大型專案中會極大降低程式碼的可閱讀性、增加開發難度

這是軟體開發的災難

(補充:透過環境變數PYTHONPATH 指定目錄本質上也是sys。path 上append)

使用相對路徑

import 。task。xxx

from 。。 import util

同樣非常不推薦這樣做,特別是我們只是想臨時debug一下子模組中的包的時候,修改好後還需要改回去,十分繁瑣,還有可能帶來不一致的問題

這是軟體開發的災難

[ 推薦做法 ]

使用

python -m task。main

執行程式

此時

sys.path

就在執行這行程式碼時所在的目錄,也就是

working directory

, 而不會進入main所在的位置,這是十分簡單的解決方法,不會帶來新的問題,但是我們卻看到許多工作大量使用

`sys。path。append(‘。。’)

importlib。import_module

之類的動態程式碼解決這類問題,這種動態性的修改會給軟體的維護帶來很多不必要的麻煩

這是軟體開發的災難

Python和 Java的import機制不同點對比

Python沒有Java不引用直接透過絕對路徑呼叫的方式

Java 不能重新命名,Python 可以 import

original_name

as

new_name

Java不能 import package,而python可以import package,python package的內容在

__init__。py

中定義

可以認為python import 一個 package(一個資料夾)就是在import 那個資料夾下的

__init__。py

,這個檔案在python2 中必須顯式提供,在python3中會預設有個空的。

需要注意的是如果

__init__。py

中什麼都沒有,那麼import這個包是沒什麼用的,我們通常會在

__init__。py

中 import這個包內的一些函式/類,這樣可以減少import的深度,用好它是提高程式碼可讀性的有力封裝工具

Python import 後仍然要使用全名

import torch。nn。Conv

java:

Conv。xxxx

python :

torch。nn。Conv。xxxx

這種情況推薦重新命名

[順便說一下] Python import 和c/c++ #include 對比

簡單來說include 是把檔案直接拼接進來,再透過編譯器編譯,所以規範的c語言標頭檔案中不能出現函式/變數的定義,只能出現宣告,否則這個標頭檔案中定義的函式/變數就會因為被多個cpp/c檔案#inlcude而被編譯多次,這樣所有編譯好的中間檔案會在連結時出現重定義的錯誤。這個錯誤常被初學者誤解,認為加入標頭檔案保護就可以解決,例如:

#ifndef SOME_CLASS_H

#define SOME_CLASS_H

// user code

#endif

非常遺憾,加入標頭檔案保護也不能避免連結時出現的重定義錯誤(它只能避免編譯時的重定義,而不能避免連結時的),因為只要有多個檔案include這個標頭檔案,裡面的東西仍然會被多次編譯,最後在連結時出錯。

理解include機制是做c/c++程式開發必須的, 它告訴我們標頭檔案裡面什麼能寫,什麼不能寫。但篇幅有限,這裡就不展開講了,下一篇文章可以介紹一下include機制。

標簽: py  import  目錄  path  xxx