用 Python 解析命令行參數
借鑒 C 語言的歷史,學習如何用 Python 編寫有用的 CLI 程序。
https://linux.cn/article-12286-1.html
作者:Erik O"shaughnessy
譯者:Xingyu.Wang
本文的目標很簡單:幫助新的 Python 開發者了解一些關於命令行介面(CLI)的歷史和術語,並探討如何在 Python 中編寫這些有用的程序。
最初……
首先,從Unix的角度談談命令行界面設計。
Unix 是一種計算機操作系統,也是 Linux 和 macOS(以及許多其他操作系統)的祖先。在圖形用戶界面之前,用戶通過命令行提示符與計算機進行交互(想想如今的Bash環境)。在 Unix 下開發這些程序的主要語言是C,它的功能非常強大。
因此,我們至少應該了解C 程序的基礎知識。
假設你沒有讀過上面那個鏈接的內容,C 程序的基本架構是一個叫做main的函數,它的簽名是這樣的。
?int main(int argc, char **argv)
?{
?...
?}
對於 Python 程序員來說,這應該不會顯得太奇怪。C 函數首先有一個返回類型、一個函數名,然後是括弧內的類型化參數。最後,函數的主體位於大括弧之間。函數名main是運行時鏈接器(構造和運行程序的程序)如何決定從哪裡開始執行你的程序。如果你寫了一個 C 程序,而它沒有包含一個名為main的函數,它將什麼也做不了。傷心。
函數參數變數argc和argv共同描述了程序被調用時用戶在命令行輸入的字元串列表。在典型的 Unix 命名傳統中,argc的意思是「參數計數(argument count)」,argv的意思是「參數向量(argument vector)」。向量聽起來比列表更酷,而argl聽起來就像一個要勒死的求救聲。我們是 Unix 系統的程序員,我們不求救。我們讓其他人哭著求救。
再進一步
$ ./myprog foo bar -x baz
如果myprog是用 C 語言實現的,則argc的值是 5,而argv是一個有五個條目的字元指針數組。(不要擔心,如果這聽起來過於技術,那換句話說,這是一個由五個字元串組成的列表。)向量中的第一個條目argv[0]是程序的名稱。argv的其餘部分包含參數。
?argv[0] == "./myprog"
?argv[1] == "foo"
?argv[2] == "bar"
?argv[3] == "-x"
?argv[4] == "baz"
?/* 註:不是有效的 C 代碼 */
在 C 語言中,你有很多方法來處理argv中的字元串。你可以手動地循環處理數組argv,並根據程序的需要解釋每個字元串。這相對來說比較簡單,但會導致程序的介面大相徑庭,因為不同的程序員對什麼是「好」有不同的想法。
include
/* 一個列印 argv 內容的簡單 C 程序。*/
int main(int argc, char **argv) {
int i;
for(i=0; i
printf("%s\n", argv[i]);
}
早期對命令行標準化的嘗試
命令行武器庫中的下一個武器是一個叫做getopt的C 標準庫函數。這個函數允許程序員解析開關,即前面帶破折號的參數(比如-x),並且可以選擇將後續參數與它們的開關配對。想想/bin/ls -alSh這樣的命令調用,getopt就是最初用來解析該參數串的函數。使用getopt使命令行的解析變得相當簡單,並改善了用戶體驗(UX)。
include
#include
#define OPTSTR "b:f:"
extern char *optarg;
int main(int argc, char **argv) {
int opt;
char *bar = NULL;
char *foo = NULL;
while((opt=getopt(argc, argv, OPTSTR)) != EOF)
switch(opt) {
case "b":
bar = optarg;
break;
case "f":
foo = optarg;
break;
case "h":
default":
fprintf(stderr, "Huh? try again.");
exit(-1);
/* NOTREACHED */
}
printf("%s\n", foo ? foo : "Empty foo");
printf("%s\n", bar ? bar : "Empty bar");
}
就個人而言,我希望?Python 有開關,但這永遠、永遠不會發生。
GNU 時代
GNU項目出現了,並為他們實現的傳統 Unix 命令行工具引入了更長的格式參數,比如--file-format foo。當然,我們這些 Unix 程序員很討厭這樣,因為打字太麻煩了,但是就像我們這些舊時代的恐龍一樣,我們輸了,因為用戶喜歡更長的選項。我從來沒有寫過任何使用 GNU 風格選項解析的代碼,所以這裡沒有代碼示例。
GNU 風格的參數也接受像-f foo這樣的短名,也必須支持。所有這些選擇都給程序員帶來了更多的工作量,因為他們只想知道用戶要求的是什麼,然後繼續進行下去。但用戶得到了更一致的用戶體驗:長格式選項、短格式選項和自動生成的幫助,使用戶不必再試圖閱讀臭名昭著的難以解析的手冊頁面(參見ps這個特別糟糕的例子)。
但我們正在討論 Python?
你現在已經接觸了足夠多(太多?)的命令行的歷史,對如何用我們最喜歡的語言來編寫 CLI 有了一些背景知識。Python 在命令行解析方面給出了類似的幾個選擇:自己解析,自給自足(batteries-included)的方式,以及大量的第三方方式。你選擇哪一種取決於你的特定情況和需求。
首先,自己解析
你可以從sys模塊中獲取程序的參數。
import sys
if __name__ == "__main__":
?for value in sys.argv:
? ? ?print(value)
自給自足
在 Python 標準庫中已經有幾個參數解析模塊的實現:getopt、optparse,以及最近的argparse。argparse允許程序員為用戶提供一致的、有幫助的用戶體驗,但就像它的 GNU 前輩一樣,它需要程序員做大量的工作和「模板代碼」才能使它「奏效」。
from argparse import ArgumentParser
if __name__ == "__main__":
?argparser = ArgumentParser(description="My Cool Program")
?argparser.add_argument("--foo", "-f", help="A user supplied foo")
?argparser.add_argument("--bar", "-b", help="A user supplied bar")
?results = argparser.parse_args()
?print(results.foo, results.bar)
好處是當用戶調用--help時,有自動生成的幫助。但是自給自足(batteries included)的優勢呢?有時,你的項目情況決定了你對第三方庫的訪問是有限的,或者說是沒有,你不得不用 Python 標準庫來「湊合」。
CLI 的現代方法
然後是Click。Click框架使用裝飾器的方式來構建命令行解析。突然間,寫一個豐富的命令行界面變得有趣而簡單。在裝飾器的酷炫和未來感的使用下,很多複雜的東西都消失了,用戶驚嘆於自動支持關鍵字補完以及上下文幫助。所有這些都比以前的解決方案寫的代碼更少。任何時候,只要你能寫更少的代碼,還能把事情做好,就是一種勝利。而我們都想要勝利。
import click
@click.command()
@click.option("-f", "--foo", default="foo", help="User supplied foo.")
@click.option("-b", "--bar", default="bar", help="User supplied bar.")
def echo(foo, bar):
? """My Cool Program
? It does stuff. Here is the documentation for it.
? """
? print(foo, bar)
if __name__ == "__main__":
? echo()
你可以在@click.option裝飾器中看到一些與argparse相同的模板代碼。但是創建和管理參數分析器的「工作」已經被抽象化了。現在,命令行參數被解析,而值被賦給函數參數,從而函數echo被魔法般地調用。
在Click介面中添加參數就像在堆棧中添加另一個裝飾符並將新的參數添加到函數定義中一樣簡單。
但是,等等,還有更多!
Typer建立在Click之上,是一個更新的 CLI 框架,它結合了Click的功能和現代 Python類型提示。使用Click的缺點之一是必須在函數中添加一堆裝飾符。CLI 參數必須在兩個地方指定:裝飾符和函數參數列表。Typer免去你造輪子去寫 CLI 規範,讓代碼更容易閱讀和維護。
import typer
cli = typer.Typer()
@cli.command()
def echo(foo: str = "foo", bar: str = "bar"):
? """My Cool Program
? It does stuff. Here is the documentation for it.
? """
? print(foo, bar)
if __name__ == "__main__":
? cli()
是時候開始寫一些代碼了
哪種方法是正確的?這取決於你的用例。你是在寫一個只有你才會使用的快速而粗略的腳本嗎?直接使用sys.argv然後繼續編碼。你需要更強大的命令行解析嗎?也許argparse就夠了。你是否有很多子命令和複雜的選項,你的團隊是否會每天使用它?現在你一定要考慮一下Click或Typer。作為一個程序員的樂趣之一就是魔改出替代實現,看看哪一個最適合你。
最後,在 Python 中有很多用於解析命令行參數的第三方軟體包。我只介紹了我喜歡或使用過的那些。你喜歡和/或使用不同的包是完全可以的,也是我們所期望的。我的建議是先從這些包開始,然後看看你最終的結果。
去寫一些很酷的東西吧。
via:https://opensource.com/article/20/6/c-python-cli
作者:Erik O"Shaughnessy選題:lujun9972譯者:wxy校對:wxy
本文由LCTT原創編譯,Linux中國榮譽推出