借鑑 C 語言的歷史,學習如何用 Python 編寫有用的 CLI 程序。html
本文的目標很簡單:幫助新的 Python 開發者瞭解一些關於命令行接口(CLI)的歷史和術語,並探討如何在 Python 中編寫這些有用的程序。python
首先,從 Unix 的角度談談命令行界面設計。linux
Unix 是一種計算機操做系統,也是 Linux 和 macOS(以及許多其餘操做系統)的祖先。在圖形用戶界面以前,用戶經過命令行提示符與計算機進行交互(想一想現在的 Bash 環境)。在 Unix 下開發這些程序的主要語言是 C,它的功能很是強大。git
所以,咱們至少應該瞭解 C 程序的基礎知識。程序員
假設你沒有讀過上面那個連接的內容,C 程序的基本架構是一個叫作 main
的函數,它的簽名是這樣的。github
int main(int argc, char **argv)
{
...
}
複製代碼
對於 Python 程序員來講,這應該不會顯得太奇怪。C 函數首先有一個返回類型、一個函數名,而後是括號內的類型化參數。最後,函數的主體位於大括號之間。函數名 main
是運行時連接器(構造和運行程序的程序)如何決定從哪裏開始執行你的程序。若是你寫了一個 C 程序,而它沒有包含一個名爲 main
的函數,它將什麼也作不了。傷心。數組
函數參數變量 argc
和 argv
共同描述了程序被調用時用戶在命令行輸入的字符串列表。在典型的 Unix 命名傳統中,argc
的意思是「參數計數」,argv
的意思是「參數向量」。向量聽起來比列表更酷,而 argl
聽起來就像一個要勒死的求救聲。咱們是 Unix 系統的程序員,咱們不求救。咱們讓其餘人哭着求救。ruby
$ ./myprog foo bar -x baz
複製代碼
若是 myprog
是用 C 語言實現的,則 argc
的值是 5,而 argv
是一個有五個條目的字符指針數組。(不要擔憂,若是這聽起來過於技術,那換句話說,這是一個由五個字符串組成的列表。)向量中的第一個條目 argv[0]
是程序的名稱。argv
的其他部分包含參數。bash
argv[0] == "./myprog"
argv[1] == "foo"
argv[2] == "bar"
argv[3] == "-x"
argv[4] == "baz"
/* 注:不是有效的 C 代碼 */
複製代碼
在 C 語言中,你有不少方法來處理 argv
中的字符串。你能夠手動地循環處理數組 argv
,並根據程序的須要解釋每一個字符串。這相對來講比較簡單,但會致使程序的接口截然不同,由於不一樣的程序員對什麼是「好」有不一樣的想法。架構
include <stdio.h>
/* 一個打印 argv 內容的簡單 C 程序。 */
int main(int argc, char **argv) {
int i;
for(i=0; i<argc; i++)
printf("%s\n", argv[i]);
}
複製代碼
命令行武器庫中的下一個武器是一個叫作 getopt 的 C 標準庫函數。這個函數容許程序員解析開關,即前面帶破折號的參數(好比 -x
),而且能夠選擇將後續參數與它們的開關配對。想一想 /bin/ls -alSh
這樣的命令調用,getopt
就是最初用來解析該參數串的函數。使用 getopt
使命令行的解析變得至關簡單,並改善了用戶體驗(UX)。
include <stdio.h>
#include <getopt.h>
#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 項目出現了,併爲他們實現的傳統 Unix 命令行工具引入了更長的格式參數,好比--file-format foo
。固然,咱們這些 Unix 程序員很討厭這樣,由於打字太麻煩了,可是就像咱們這些舊時代的恐龍同樣,咱們輸了,由於用戶喜歡更長的選項。我歷來沒有寫過任何使用 GNU 風格選項解析的代碼,因此這裏沒有代碼示例。
GNU 風格的參數也接受像 -f foo
這樣的短名,也必須支持。全部這些選擇都給程序員帶來了更多的工做量,由於他們只想知道用戶要求的是什麼,而後繼續進行下去。但用戶獲得了更一致的用戶體驗:長格式選項、短格式選項和自動生成的幫助,使用戶沒必要再試圖閱讀臭名昭著的難以解析的手冊頁面(參見 ps 這個特別糟糕的例子)。
你如今已經接觸了足夠多(太多?)的命令行的歷史,對如何用咱們最喜歡的語言來編寫 CLI 有了一些背景知識。Python 在命令行解析方面給出了相似的幾個選擇:本身解析,自給自足的方式,以及大量的第三方方式。你選擇哪種取決於你的特定狀況和需求。
你能夠從 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
時,有自動生成的幫助。可是自給自足的優點呢?有時,你的項目狀況決定了你對第三方庫的訪問是有限的,或者說是沒有,你不得不用 Python 標準庫來「湊合」。
而後是 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: opensource.com/article/20/…
做者:Erik O'Shaughnessy 選題:lujun9972 譯者:wxy 校對:wxy