對 Python 程序來說火焰,完備的命令行界面可以提升團(tuán)隊的工作效率拆融,減少調(diào)用時可能碰到的困擾染突。今天淘太,我們就來教大家如何設(shè)計功能完整的 Python 命令行界面姻僧。
對 Python 開發(fā)者來說用的最多的界面恐怕還是命令行规丽。就拿我參與的機(jī)器學(xué)習(xí)項目來說,訓(xùn)練模型和評估算法的精確度都是通過在命令行界面運(yùn)行腳本來完成的段化。
所以調(diào)用一個 Python 腳本的時候我們希望這段腳本有一個盡量簡潔方便調(diào)用的接口嘁捷。尤其是團(tuán)隊中有多名開發(fā)者的時候這一點對提升團(tuán)隊的工作效率很重要。
要讓一段腳本方便調(diào)用總的來說有四個原則需要遵守:
- 提供默認(rèn)參數(shù)值
- 處理調(diào)用出錯的情況显熏,比如缺少參數(shù)雄嚣、參數(shù)類型錯誤或者找不到文件等
- 在文檔中說明各個參數(shù)和選項的用法
- 如果執(zhí)行時間較長應(yīng)該提供進(jìn)度條
一個簡單的例子
下面我們先通過一個簡單的例子來談?wù)勥@四個原則的具體應(yīng)用。例子中給出的腳本的功能是使用凱撒碼變換對文本進(jìn)行加密和解密喘蟆。
Caesar cipher:一種簡單的消息編碼方式缓升。在密碼學(xué)中,凱撒密碼蕴轨,移位密碼是最簡單和最廣為人知的加密技術(shù)之一港谊。
比如說我們想讓用戶通過命令行參數(shù)來選擇調(diào)用的方式是加密還是解密文本,而且用戶要從命令行傳入下面 encrypt 函數(shù)中的密匙參數(shù) key橙弱。
首先我們得在程序中拿到命令行參數(shù)歧寺。我在網(wǎng)上搜“ python 命令行參數(shù)”出來的第一個結(jié)果說讓我用 sys.argv ,那我們就來試試看它好不好用。
初級:笨辦法
其實 sys.argv 只是一個 list ,這個 list 的內(nèi)容是用戶調(diào)用腳本時所輸入的所有參數(shù)(其中也包括腳本的文件名)棘脐。
如果我像下面這樣調(diào)用加解密的腳本 caesar_script.py 的話:
> python caesar_script.py --key 23 --decrypt my secret message
pb vhfuhw phvvdjh
sys.argv 這個 list 的值就是:
['caesar_script.py', '--key', '23', '--decrypt', 'my', 'secret', 'message']
所以我們現(xiàn)在要遍歷這個 list 來找其中是否包括了“ –key ”或者“ -k ”斜筐,這樣我們就能找到密匙“ 23 ”。再找到“ –decrypt ”就能知道用戶是想要解密一段文本了(其實解密就是用密匙的相反數(shù)再加密一次)蛀缝。
完成后的代碼如下:
這段代碼基本上遵守了我們提到的四個原則:
- key 和 加密模式都設(shè)置了缺省參數(shù)
- 腳本可以處理像沒有文本或者缺少參數(shù)這樣比較基本的錯誤
- 用戶沒有給參數(shù)或者有錯的話會顯示使用幫助
> python caesar_script_using_sys_argv.py
Usage: python caesar.py [ --key <key> ] [ --encrypt|decrypt ] <text>
然而不算加密函數(shù)光處理參數(shù)我們就已經(jīng)寫了 39 行而且寫得一點也不優(yōu)雅顷链。我有膽說肯定還有更好的辦法來讀命令行參數(shù)。
中級:argparse
Python 標(biāo)準(zhǔn)庫里面提供了一個讀取命令行參數(shù)的庫——argparse 屈梁。我們來看看如果用 argparse 代碼怎么寫:
這樣寫也符合四項指導(dǎo)原則嗤练,而且對參數(shù)的說明和錯誤處理都優(yōu)于使用 sys.argv 的笨辦法:
不過我個人還是覺得代碼里第 7 行到第 13 行定義參數(shù)的部分寫得很啰嗦,而且我覺得參數(shù)應(yīng)該使用聲明式的方法來定義在讶。
高級: click
還有一個叫 click 的庫能實現(xiàn)我們想要的這些煞抬。它的基本功能和 argparse 是一樣的,但寫出來的代碼更優(yōu)雅真朗。
使用 click 改寫我們的加解密腳本之后是這樣的:
我們需要的參數(shù)和選項都用裝飾器來聲明此疹,這樣就可以在 caesar 函數(shù)里直接使用了。
上面的代碼里有幾點需要說明:
- nargs 參數(shù)是說這個參數(shù)的長度是幾個詞遮婶。默認(rèn)值是 1 不過用引號引起來的句子也只算一個詞蝗碎。這里我們設(shè)為 -1 是指不限制長度。
- --decrypt/--encrypt 這樣加一個斜杠的寫法用來指明互斥的選項旗扑,它的功能和 argparse 中的 add_mutually_exclusive_group 函數(shù)類似蹦骑。
- click.echo 是 click 提供的一個 print 功能,與 Python 2 和 3 都兼容臀防,而且有顏色高亮功能眠菇。
添加隱私功能
我們寫的是一個對文本加解密的腳本边败,但用戶卻直接把要加密的文本打出來了,這樣有別人用這個命令行的話按幾下上方向鍵就能看到我們的用戶加密了什么東西捎废,這是在是有點荒唐笑窜。
我們可以選擇把用戶要加密的文本隱藏起來,或者是從文件里讀文本登疗。這兩種方法都能解決我們的問題排截,但選擇權(quán)應(yīng)該留給用戶。
同理對于加解密的結(jié)果我們也讓用戶選擇是直接在命令行輸出還是保存成一個文件:
這里我給每個參數(shù)和選項都加上了一小段說明辐益,這樣我們的文檔能更清楚一點因為我們現(xiàn)在參數(shù)有點多了《习粒現(xiàn)在的文檔是這樣的:
兩個新的參數(shù) input_file 和 output_file 都是 click.File 類型,而且 click 幫我們處理了文件打開的讀寫方式和可能出現(xiàn)的錯誤智政,比如這樣:
如果用戶沒有提供 input_file 的話认罩,如說明文檔中所寫,則會讓用戶在命令行進(jìn)行輸入续捂,而且用戶輸入不再是明文了:
破譯密碼
假設(shè)我們現(xiàn)在是黑客垦垂,想解密但是不知道密匙該怎么辦呢?對凱撒加密的英文來說很容易牙瓢,只要調(diào)用解密函數(shù) 25 次然后看看那個結(jié)果不是亂碼就行了乔外。
要調(diào)用 25 次還要一個一個看還是太麻煩,其實只要數(shù)數(shù)哪個結(jié)果里正確的英文詞最多就行了一罩。下面我們就用 PyEnchant 來實現(xiàn)自動破譯密碼:
一氣呵成!
不過我們好像還沒有提到四項原則的最后一點:
4.如果執(zhí)行時間較長應(yīng)該提供進(jìn)度條
上面的腳本破譯 104 個詞的文本大約需要 5 秒撇簿∧粼ǎ考慮到要遍歷 25 個密匙還要數(shù)英文詞的個數(shù)這個時間并不算慢。
不過文本再長的話四瘫,比如 105 個詞的文本汉嗽,就要花 50 秒。這就有點長了找蜜,用戶可能沒有耐心等到程序運(yùn)行完就強(qiáng)退了饼暑。
所以我建議如果執(zhí)行時間長的話最好加上進(jìn)度條,關(guān)鍵是寫起來非常簡單:
不仔細(xì)看的話可能都看不出有什么區(qū)別洗做,因為區(qū)別只有四個字母 tqdm 弓叛,阿拉伯語中 tqdm 是進(jìn)度的意思。
tqdm 庫的用法非常簡單诚纸,只要把代碼中的迭代器用 tqdm 括起來就行了:
for key in tqdm(range(26)):
這樣就會在命令行輸出一個進(jìn)度條撰筷,簡單得讓人不敢相信。
其實 click 的 click.progress_bar 也有類似的功能畦徘,但我覺得 click 的進(jìn)度條不好看而且寫法比tqdm 稍微麻煩一點毕籽。
總結(jié)一下希望大家讀完這篇文章能把設(shè)計 Python 命令行的這幾個原則用到實踐中去寫出更好用的 Python 命令行抬闯。