Python 中 import module 和 package 方法簡單記錄
Python 中以 .py 結尾的檔案實際上為一系列 Python 語句的集合,其被稱為Python 指令碼(scripts),當用戶通過 python test.py 命令執行該指令碼時,實際上是由 Python 直譯器從上至下逐行對其中的語句進行執行。對於大多數 Python 指令碼而言,在開始位置一般通過 import 語句來匯入其所需要的模組。這裡記錄一下 Python 模組和其匯入相關的內容,供學習和討論。主要內容來自 The Python Tutorial -> 6.Modules.
module
在 Python 中,可以將一系列的函式或變數定義放在一個 .py 檔案中,供其他檔案進行使用,這樣的 .py 檔案稱為一個模組(module). 模組的名字即為 Python 檔名,如 test.py 即對應 test 模組,在模組內部,可以通過變數 __name__ 來對模組名進行訪問。通過 import 關鍵字,在 Python 指令碼中匯入該模組相關名字,後續可通過匯入的名字進行呼叫。兩種常見的匯入模組中名字的方式如下所示。在一個 Python 直譯器的執行過程(session)中,每個模組只會被匯入一次。在匯入過程中,模組中的內容會按照從上到下的順序進行執行和解析。
import test #匯入 test 模組,此時將名字 test 引入當前的符號表中(從而可以引用) test.doSomething() #通過 test 模組名使用其中定義的函式 doSomething from test import doSomething #從 test 中引入 doSomething,注意此時 doSomething 被加入當前符號表(可直接引用),而 test 則不會被加入(無法直接引用) doSomething() from test import * #從 test 中匯入所有的名字,除了以 '_' 開始的名字
模組作為一個 .py 檔案,也可以作為指令碼檔案使用,當使用 Python test.py 的方式執行模組時,其模組名 __name__ 會被填充為 "__main__".
使用者可以通過內建的 dir() 函式檢視模組中定義的名字,其一般有兩種用法:
dir(test) #檢視 test 模組中定義的所有名字 dir() #檢視當前已定義的所有名字,注意不包括內建定義的名字 import builtins dir(builtins) #檢視所有內建函式和變數的名字
module 的搜尋順序
當通過 import 語句匯入一個模組的名字時,Python 直譯器遵循一定的搜尋順序
1)首先搜尋 Python 自帶的模組;
2)在 sys.path 變數規定的一系列目錄中尋找名為 模組名.py 的模組檔案。sys.path 一般被初始化為 a) 包含當前執行指令碼的當前目錄;b)PythonPATH 巨集定義的路徑;c)一般還包括 site-packages 目錄,由 site 模組處理;
sys.path 的相關內容可以參考 sys.path.
package
Python 的包(package)可以被理解為一系列模組的集合。每個 Python 包需要包含有一個對應的 __init__.py 檔案,__init__.py 檔案可以為空,也可以用於執行 Python 包匯入時的初始化等操作。
test/ #一個 test 包的定義 __init__.py #必須包含有 __init__.py 檔案 t1.py #包中的模組 t2.py
當執行 package 的 import 時,Python 直譯器通過 sys.path 指定的路徑來搜尋包的路徑。使用者通過正常的 import 操作匯入包中的名字。
import test.t1 #匯入名字 test.p1,後續通過該名字進行引用 from test import t1 #同上,但匯入的名字為 p1
不同的 import 語句對於 import 物件有不同的要求。
from package1 import test #將名字 test 視為定義在包中的名字(如包中定義的函式和變數等)進行定位,若無法定位,則將其視為一個模組進行載入,若載入失敗,則會丟擲 ImportError 異常 import package1.package2.test #語句要求除了最後一個名字 test 外,前面所有的名字均需要為 Python 包,最後一個名字可以為一個模組或一個包,但不能為定義的函式或變數。
當通過 from test import * 語句匯入包中的模組時,若 __init__.py 中定義了 __all__ 變數,則 __all__ 變數中包含有的模組名會被匯入,若沒有定義 __all__ 變數,則上述匯入語句不會匯入 test 包的任何子模組,其僅將包名 test 匯入當前符號表,同時會匯入包中定義的變數和函式等名字(如__init__.py 檔案中包含的名字)
當 Python 包被組織為目錄結構時,可以通過決定路徑或相對路徑來進行 import.相對路徑指定從 package 開始到具體模組名的路徑,而相對路徑以 "." 開始,相對於當前模組位置來進行路徑搜尋。
from test import t1 #當 test 可以通過 sys.path 定位時,可通過絕對路徑進行匯入 from . import t2 #在模組 t1 中可以通過相對路徑對 t2 進行匯入
Python 指令碼的編譯
為了提升 Python 直譯器載入模組的速度,模組的定義通常被編譯並快取,編譯好的模組檔案位於目錄 __pycache__ 目錄下,並以 module.version.pyc 的格式進行命名,module 對應模組名,verison 對應編譯資訊,.pyc 為對應的字尾。Python 通過檢查原始檔的修改日期和編譯的 version 資訊來決定是否需要進行重新編譯。
問題
當存在當前一個目錄結構的 python 工程時,如何從 main.py 中呼叫 func.py 中定義的函式?
src/ #源目錄 test/ func.py #定義了一些函式 main.py #執行指令碼
1.對於 Python 3.3+ 的直譯器版本,可以直接在 main.py 中使用 import 語句,即 from test import func 語句可直接匯入 func 模組,進而使用 func 名字來使用 func.py 中定義的函式等內容。這是由於 Python 3.3+ 中允許將不包含 __init__.py 檔案的目錄視為一個包,其被稱為 namespace packages. 這種定義包的方式一般用於 namespace 的共享,不推薦平常使用。具體可參考:Is __init__.py not required for packages in Python 3.3+;
2.由於 module 的搜尋路徑中包含有 sys.path 中定義的路徑,故而可以將 test 目錄加入 sys.path 路徑中,從而使得直譯器在搜尋模組時可以直接定位 func.py. 即通過 sys.path.insert(0, './test/'); import func 即可。但該方法一般比較使用於小專案的構建和內部使用的 trick;
3.將 test 作為一個包進行處理。在 Python 中,包含有 __init__.py 檔案的目錄即被視為一個 Python 包,之後即可通過對包中的模組匯入方式進行引用。故而可以在 test/ 目錄下加入 __init__.py 檔案,此時 test 被視為一個 Python 包,可以通過 import test.func as func 或者 from test import func 來使用 func 模組中定義的內容;
參考:
IMPORTERROR: ATTEMPTED RELATIVE IMPORT WITH NO KNOWN PARENT PACKAGE
Is __init__.py not required for packages in Python 3.3+
關於 import module 和 from module import 可以參考:Use 'import module' or 'from module import'?