1. 程式人生 > 程式設計 >Python 中 -m 的典型用法、原理解析與發展演變

Python 中 -m 的典型用法、原理解析與發展演變

在命令列中使用 Python 時,它可以接收大約 20 個選項(option),語法格式如下:

python [-bBdEhiIOqsSuvVWx?] [-c command | -m module-name | script | - ] [args]

本文想要聊聊比較特殊的“-m”選項: 關於它的典型用法、原理解析與發展演變的過程。

首先,讓我們用“--help”來看看它的解釋:

-m mod run library module as a script (terminates option list)

"mod"是“module”的縮寫,即“-m”選項後面的內容是 module(模組),其作用是把模組當成指令碼來執行。

“terminates option list”意味著“-m”之後的其它選項不起作用,在這點上它跟“-c”是一樣的,都是“終極選項”。官方把它們定義為“介面選項”(Interface options),需要區別於其它的普通選項或通用選項。

-m 選項的五個典型用法

Python 中有很多使用 -m 選項的場景,相信大家可能會用到或者看見過,我在這裡想分享 5 個。

在 Python3 中,只需一行命令就能實現一個簡單的 HTTP 服務:

python -m http.server 8000

# 注:在 Python2 中是這樣
python -m SimpleHTTPServer 8000

執行後,在本機開啟“ http://localhost:8000 ”,或者在區域網內的其它機器上開啟“ http://本機ip:8000 ”,就能訪問到執行目錄下的內容,例如下圖就是我本機的內容:

與此類似,我們只需要一行命令“python -m pydoc -p xxx”,就能生成 HTML 格式的官方幫助文件,可以在瀏覽器中訪問。

上面的命令執行了 pydoc 模組,會在 9000 埠啟動一個 http 服務,在瀏覽器中開啟,我的結果如下:

它的第三個常見用法是執行 pdb 的除錯命令“python -m pdb xxx.py”,以除錯模式來執行“xxx.py”指令碼:

第四個同樣挺有用的場景是用 timeit 在命令列中測試一小段程式碼的執行時間。以下的 3 段程式碼,用不同的方式拼接 “0-1-2-……-99” 數字串。可以直觀地看出它們的效率差異:

最後,還有一種常常被人忽略的場景:“python -m pip install xxx”。我們可能會習慣性地使用“pip install xxx”,或者做了版本區分時用“pip3 install xxx”,總之不在前面用“python -m”做指定。但這種寫法可能會出問題。

很巧合的是,在本月初(2019.11.01),Python 的核心開發者、第一屆指導委員會 五人成員之一的 Brett Cannon 專門寫了一篇部落格《 Why you should use "python -m pip" 》,提出應該使用“python -m pip”的方式,並做了詳細的解釋。

他的主要觀點是:在存在多個 Python 版本的環境中,這種寫法可以精確地控制三方庫的安裝位置。例如用“python3.8 -m pip”,可以明確指定給 3.8 版本安裝,而不會混淆成其它的版本。

(延伸閱讀:關於 Brett 的文章,這有一篇簡短的歸納《 原來我一直安裝 Python 庫的姿勢都不對呀! 》)

-m 選項的兩種原理解析

看了前面的幾種典型用法,你是否開始好奇: “-m”是怎麼運作的?它是怎麼實現的?

對於“python -m name”,一句話解釋: Python 會檢索 sys.path ,查詢名字為“name”的模組或者包(含名稱空間包),並將其內容當成“__main__”模組來執行。

1、對於普通模組

以“.py”為字尾的檔案就是一個模組,在“-m”之後使用時,只需要使用模組名,不需要寫出字尾,但前提是該模組名是有效的,且不能是用 C 語言寫成的模組。

在“-m”之後,如果是一個無效的模組名,則會報錯“No module named xxx”。

如果是一個帶字尾的模組,則首先會匯入該模組,然後可能報錯:Error while finding module specification for 'xxx.py' (AttributeError: module 'xxx' has no attribute '__path__'。

對於一個普通模組,有時候這兩種寫法表面看起來是等效的:

兩種寫法都會把定位到的模組腳本當成主程式入口來執行,即在執行時,該指令碼的 __name__ 都是”__main__“,跟 import 匯入方式是不同的。

但它的前提是:在執行目錄中存在著“test.py”,且只有唯一的“test”模組。對於本例,如果換一個目錄執行的話,“python test.py”當然會報找不到檔案的錯誤,然而,“python -m test”卻不會報錯,因為直譯器在遍歷 sys.path 時可以找到同名的“test”模組,並且執行:

由此差異,我們其實可以總結出“-m”的用法: 已知一個模組的名字,但不知道它的檔案路徑,那麼使用“-m”就意味著交給直譯器自行查詢,若找到,則當成指令碼執行。

以前文的“python -m http.server 8000”為例,我們也可以找到“server”模組的絕對路徑,然後執行,儘管這樣會變得很麻煩。

那麼,“-m”方式與直接執行指令碼相比,在實現上有什麼不同呢?

直接執行指令碼時,相當於給出了指令碼的完整路徑(不管是絕對路徑還是相對路徑),直譯器根據 檔案系統的查詢機制, 定位到該指令碼,然後執行 使用“-m”方式時,直譯器需要在不 import 的情況下,在 所有模組名稱空間 中查詢,定位到指令碼的路徑,然後執行。為了實現這個過程,直譯器會藉助兩個模組: pkgutilrunpy ,前者用來獲取所有的模組列表,後者根據模組名來定位並執行指令碼 2、對於包內模組

如果“-m”之後要執行的是一個包,那麼直譯器經過前面提到的查詢過程,先定位到該包,然後會去執行它的“__main__”子模組,也就是說,在包目錄下需要實現一個“__main__.py”檔案。

換句話說,假設有個包的名稱是“pname”,那麼, “python -m pname”,其實就等效於“python -m pname.__main__”。

仍以前文建立 HTTP 服務為例,“http”是 Python 內建的一個包,它沒有“__main__.py”檔案,所以使用“-m”方式執行時,就會報錯:No module named http.__main__; 'http' is a package and cannot be directly executed。

作為對比,我們可以看看前文提到的 pip,它也是一個包,為什麼“python -m pip”的方式可以使用呢?當然是因為它有“__main__.py”檔案:

“python -m pip”實際上執行的就是這個“__main__.py”檔案,它主要作為一個呼叫入口,呼叫了核心的"pip._internal.main"。

http 包因為沒有一個統一的入口模組,所以採用了“python -m 包.模組”的方式,而 pip 包因為有統一的入口模組,所以加了一個“__main__.py”檔案,最後只需要寫“python -m 包”,簡明直觀。

-m 選項的十年演變過程

最早引入 -m 選項的是 Python 2.4 版本(2004年),當時功能還挺受限,只能作用於普通的內建模組(如 pdb 和 profile)。

隨後,知名開發者 Nick Coghlan 提出的《PEP 338 -- Executing modules as scripts 》把它的功能提升了一個臺階。這個 PEP 在 2004 年提出,最終實現在 2006 年的 2.5 版本。

(插個題外話:Nick Coghlan 是核心開發者中的核心之一,也是第一屆指導委員會的五人成員之一。記得當初看材料,他是在 2005 年被選為核心開發者的,這時間與 PEP-338 的時間緊密貼合)

這個 PEP 的幾個核心點是:

  • 結合了 PEP-302 的新探針機制(new import hooks),提升瞭解釋器查詢包內模組的能力
  • 結合了其它的匯入機制(例如 zipimport 和凍結模組(frozen modules)),拓展瞭解釋器查詢模組的範圍與精度
  • 開發了新的 runpy.run_module(modulename) 來實現本功能,而不用修改 CPython 直譯器,如此可方便移植到其它直譯器

至此,-m 選項使得 Python 可以在所有的名稱空間內定位到命令列中給定的模組。

2009 年,在 Python 3.1 版本中,只需給定包的名稱,就能定位和執行它的“__main__”子模組。2014 年,-m 擴充套件到支援名稱空間包。

至此,經過十年的發展演變,-m 選項變得功能齊全,羽翼豐滿。

最後,我們來個 ending 吧:-m 選項可能看似不起眼,但它絕對是最特別的選項之一,它使得在命令列中,使用內建模組、標準包與三方庫時變得更輕鬆便利。有機會就多用一下吧,體會它帶來的愉悅體驗。

參考材料

https://docs.python.org/3.7/using/cmdline.html#cmdoption-m

https://snarky.ca/why-you-should-use-python-m-pip

https://www.python.org/dev/peps/pep-0338/

總結

以上所述是小編給大家介紹的Python 中 -m 的典型用法、原理解析與發展演變,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回覆大家的。在此也非常感謝大家對我們網站的支援!
如果你覺得本文對你有幫助,歡迎轉載,煩請註明出處,謝謝!