該文章內容整理自《Python程式設計:從入門到實踐》、《流暢的Python》、以及網上各大部落格
Python 提供了強大的模組(Modules)支援,不僅 Python 標準庫中包含了大量的模組(即標準模組),還有大量的第三方模組,開發者自己也可以開發自定義模組。通過這些強大的模組可以極大地提高開發者的開發效率。模組可以理解爲是對程式碼更高階的封裝,即把能夠實現某一特定功能的程式碼編寫在同一個 .py 檔案中,並將其作爲一個獨立的模組,這樣既可以方便其它程式或指令碼匯入並使用,同時還能有效避免函數名和變數名發生衝突
import用於匯入模組,其具體用法爲
預設情況下,模組在第一次被匯入之後,其他的匯入都不再有效。如果此時在另一個視窗中改變並儲存了模組的原始碼檔案,也無法更新該模組。這樣設計的原因在於,匯入是一個開銷很大的操作(匯入必須找到檔案,將其編譯成位元組碼,並且執行程式碼),以至於每個檔案、每個程式執行不能夠重複多於一次
若此時使用import匯入模組後模組發生變化,而需要重新匯入新的模組,則可以通過reload()進行重新載入。重新載入包括最初匯入模組時應用的分析過程和初始化過程。這樣就允許在不退出直譯器的情況下重新載入已更改的Python模組。但是注意
在 Python2 中reload()是內建函數,能夠直接使用。但是 Python3 把reload()內建函數移到了imp標準庫模組中,因而使用前需要從imp中匯入
import語句結合了兩個操作:先搜尋指定名稱的模組,然後將搜尋結果系結到當前作用域中的名稱。而內建函數__import__()只搜尋指定名稱的模組,並且,import語句執行過程中是呼叫__import__()來完成模組檢索的
import語句屬於靜態匯入,而__import__()函數用於動態載入模組。一般形式如下,它返回匯入的模組,一般會賦值給變數,以便後期使用
__import__(name, globals=None, locals=None, fromlist=(), level=0)
其中
# from spam.ham import eggs, sausage as saus
_temp = __import__('spam.ham', globals(), locals(), ['eggs', 'sausage'], 0)
eggs = _temp.eggs
saus = _temp.sausage
但是在一般業務邏輯中不建議直接使用__import__(),而是採用importlib模組的import_module()函數來代替。其一般形式爲
import_module(name, package=None)
此時只需要知道模組名稱就可以。如 import_module(’…mod’, ‘pkg.subpkg’) 將會匯入 pkg.mod
import不僅能匯入一個模組,還能夠匯入包。包是一個含__init__.py的資料夾,其中存放了多個模組檔案(這是 Python 2 的規定,而在 Python 3 中__init__.py 對包來說並不是必須的)。雖然包是一個包含多個模組的資料夾,它的本質依然是模組,因此包中也可以包含包。另外,相比模組和包,庫是一個更大的概念,例如在 Python 標準庫中的每個庫都有好多個包,而每個包中都有若幹個模組
若要建立一個包,首先需要新增__init__.py檔案。一般而言,此檔案中無需編寫任何程式碼
因爲包其實本質上還是模組,因此匯入模組的語法同樣也適用於匯入包。所以匯入包的方法有:
但若只想匯入包內的個別模組,一種方法是直接在呼叫包的檔案內使用import逐個匯入,但包內模組較多、目錄複雜時,這種方法較爲複雜。此時就需要編寫__init__.py檔案。__init__.py 不同於其他模組檔案,它的模組名不是__init__,而是它所在的包名。例如,在 settings 包中的 __init__.py 檔案的模組名就是 settings。當目錄中包含__init__.py,並用 import 直接匯入該目錄時,Python會首先執行__init__.py裏面的程式碼。因而可在__init__.py統一匯入。另外,在__init__.py內匯入模組需要匯入完整的路徑名稱,如包目錄爲
└── mypackage
├── subpackage_1
│ ├── test11.py
├── subpackage_2
└── test21.py
則__init__.py爲
# from subpackage_1 import test11 # error
from mypackage.subpackage_1 import test11
也可以在 mypackage 目錄的 __init__.py 檔案匯入對於的子目錄,在子目錄的 __init__.py 中才匯入具體某個模組,使得包內模組匯入更爲精準和便捷
當使用 import 語句匯入模組後,Python 會按照以下順序查詢指定的模組檔案:
這些目錄都儲存在標準模組 sys 的 sys.path 列表變數中,通過此變數可以看到指定程式檔案支援查詢的所有目錄。換句話說,如果要匯入的模組沒有儲存在 sys.path 列表的目錄中,那麼匯入該模組並執行程式時 Python 直譯器就會拋出 ModuleNotFoundError(未找到模組)異常。而解決未找到模組異常的方法有 3 種:
import sys
sys.path.append('D:\\python_module')
另外,在 sys 模組和 os 模組中有一些常用的函數:
Python 的模組中內建有多個雙下劃線成員,分別爲
'demo.__doc__'
def f():
'f.__doc__'
class c:
'c.__doc__'
print(demo.__doc__)
print(f.__doc__)
print(c.__doc__)
# 或用help()函數檢視
print(help(demo))
print(help(f))
print(help(c))
Python程式執行時不需要編譯成二進制程式碼,而直接從原始碼執行程式,簡單來說是,Python直譯器將原始碼轉換爲位元組碼,然後再由直譯器來執行這些位元組碼。直譯器的具體工作爲:
1、完成模組的載入和鏈接
2、將原始碼編譯爲PyCodeObject物件(即位元組碼),寫入記憶體中,供CPU讀取
3、從記憶體中讀取並執行,結束後將PyCodeObject寫回硬碟當中,也就是複製到.pyc或.pyo檔案中,以儲存當前目錄下所有指令碼的位元組碼檔案
之後若再次執行該指令碼,它先檢查本地是否有上述位元組碼檔案和該位元組碼檔案的修改時間是否在其原始檔之後,是就直接執行,否則重複上述步驟。而__pycache__資料夾的意義在於,第一次執行程式碼的時候,Python直譯器已經把編譯的位元組碼放在__pycache__資料夾中,這樣以後再次執行的話,如果被呼叫的模組未發生改變,那就直接跳過編譯這一步,直接去__pycache__資料夾中去執行相關的 *.pyc 檔案,大大縮短了專案執行前的準備時間
另外,爲了提高模組載入的速度,每個模組都會在__pycache__資料夾中放置該模組的預編譯模組,命名爲 module.version.pyc,version 是模組的預編譯版本編碼,一般都包含 Python 的版本號。這種命名規則可以保證不同版本的模組和不同版本的 Python 編譯器的預編譯模組可以共存。預編譯模組也是跨平臺的,所以不同的模組是可以在不同的系統和不同的架構之間共用的
如在匯入模組時,模組所在資料夾將自動生成一個對應的__pycache__\module_name.cpython-36.pyc檔案;而在匯入包時,會在包的目錄下生成一個__pycache__/__init__.cpython-36.pyc 檔案
Python 在兩種情況下不檢查快取。第一種,從命令列中直接載入的模組總是會重新編譯並且結果不儲存。第二種,如果沒有源模組,則不會檢查快取。爲了支援無原始碼的部署方式,應該將預編譯模組放在原始碼資料夾中而不是 __pycache__ 中,並且不要包含原始碼模組
也可以使用 -O 和 -OO 參數來降低預編譯模組的大小。-O 會去除 assert 語句,-OO 會去除 assert 語句和 __doc__ 字串。優化模組的後綴名是 .pyo。但是,.pyo 和 .pyc 檔案的執行速度不會比 .py 檔案快,快的地方在於模組載入的速度。compileall 模組可以用來把某個資料夾的中的所有檔案都編譯成爲 .pyc 或者 .pyo 檔案