在命令行中使用 Python 時,它能夠接收大約 20 個選項(option),語法格式以下:html
python [-bBdEhiIOqsSuvVWx?] [-c command | -m module-name | script | - ] [args]
本文想要聊聊比較特殊的「-m」選項:關於它的典型用法、原理解析與發展演變的過程。python
首先,讓咱們用「--help」來看看它的解釋:瀏覽器
> -m mod run library module as a script (terminates option list)測試
"mod"是「module」的縮寫,即「-m」選項後面的內容是 module(模塊),其做用是把模塊當成腳原本運行。.net
「terminates option list」意味着「-m」以後的其它選項不起做用,在這點上它跟「-c」是同樣的,都是「終極選項」。官方把它們定義爲「接口選項」(Interface options),須要區別於其它的普通選項或通用選項。命令行
Python 中有不少使用 -m 選項的場景,相信你們可能會用到或者看見過,我在這裏想分享 5 個。翻譯
在 Python3 中,只需一行命令就能實現一個簡單的 HTTP 服務:調試
python -m http.server 8000 # 注:在 Python2 中是這樣 python -m SimpleHTTPServer 8000
執行後,在本機打開「http://localhost:8000」,或者在局域網內的其它機器上打開「http://本機ip:8000」,就能訪問到執行目錄下的內容,例以下圖就是我本機的內容:code
與此相似,咱們只須要一行命令「python -m pydoc -p xxx」,就能生成 HTML 格式的官方幫助文檔,能夠在瀏覽器中訪問。server
上面的命令執行了 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」是怎麼運做的?它是怎麼實現的?
對於「python -m name」,一句話解釋:Python 會檢索sys.path
,查找名字爲「name」的模塊或者包(含命名空間包),並將其內容當成「__main__」模塊來執行。
以「.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」方式與直接運行腳本相比,在實現上有什麼不一樣呢?
pkgutil
和 runpy
,前者用來獲取全部的模塊列表,後者根據模塊名來定位並執行腳本若是「-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 選項的是 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/
http://www.javashuo.com/article/p-ywvlfeqp-ko.html
公衆號【Python貓】, 本號連載優質的系列文章,有喵星哲學貓系列、Python進階系列、好書推薦系列、技術寫做、優質英文推薦與翻譯等等,歡迎關注哦。