如何編寫完美的 Python命令行程序?
作為 Python 開發(fā)者,我們經(jīng)常要編寫命令行程序。比如在我的數(shù)據(jù)科學(xué)項(xiàng)目中,我要從命令行運(yùn)行腳本來訓(xùn)練模型,以及計(jì)算算法的準(zhǔn)確率等。
因此,更方便更易用的腳本能夠很好地提高生產(chǎn)力,特別是在有多個(gè)開發(fā)者從事同一個(gè)項(xiàng)目的場合下。
因此,我建議你遵循以下四條規(guī)則:
盡可能提供默認(rèn)參數(shù)值所有錯(cuò)誤情況必須處理(例如,參數(shù)缺失,類型錯(cuò)誤,找不到文件)所有參數(shù)和選項(xiàng)必須有文檔不是立即完成的任務(wù)應(yīng)當(dāng)顯示進(jìn)度條
舉個(gè)簡單的例子
我們把這些規(guī)則應(yīng)用到一個(gè)具體的例子上。這個(gè)腳本可以使用凱撒加密法加密和解密消息。
假設(shè)已經(jīng)有個(gè)寫好的 encrypt 函數(shù)(實(shí)現(xiàn)如下),我們需要?jiǎng)?chuàng)建一個(gè)簡單的腳本,用來加密和解密消息。我們希望讓用戶通過命令行參數(shù)選擇加密模式(默認(rèn))和解密模式,并選擇一個(gè)秘鑰(默認(rèn)為 1)。
defencrypt(plaintext, key): cyphertext = ''for character in plaintext:if character.isalpha(): number = ord(character) number += keyif character.isupper():if number > ord('Z'): number -= 26elif number < ord('A'):number += 26elif character.islower():if number > ord('z'): number -= 26elif number < ord('a'): number += 26 character = chr(number) cyphertext += characterreturn cyphertext
我們的腳本需要做的第一件事就是獲取命令行參數(shù)的值。當(dāng)我搜索“python command line arguments”時(shí),出現(xiàn)的第一個(gè)結(jié)果是關(guān)于sys.a(chǎn)rgv的,所以我們來試試這個(gè)方法……
“初學(xué)者”的方法
sys.a(chǎn)rgv 是個(gè)列表,包含用戶在運(yùn)行腳本時(shí)輸入的所有參數(shù)(包括腳本名自身)。
例如,如果我輸入:
> pythoncaesar_script.py--key 23 --decryptmysecretmessagepbvhfuhwphvvdjh
該列表將包含:
['caesar_script.py', '--key', '23', '--decrypt', 'my', 'secret', 'message']
因此只需遍歷該參數(shù)列表,找到'--key'(或'-k')以得到秘鑰值,找到'--decrypt'以設(shè)置解密模式(實(shí)際上只需要使用秘鑰的反轉(zhuǎn)作為秘鑰即可)。
最后我們的腳本大致如下:
import sysfrom caesar_encryption import encryptdefcaesar(): key = 1is_error = Falsefor index, arg in enumerate(sys.a(chǎn)rgv):if arg in ['--key', '-k'] and len(sys.a(chǎn)rgv) > index + 1: key = int(sys.a(chǎn)rgv[index + 1])del sys.a(chǎn)rgv[index]del sys.a(chǎn)rgv[index]breakfor index, arg in enumerate(sys.a(chǎn)rgv):if arg in ['--encrypt', '-e']:del sys.a(chǎn)rgv[index]breakif arg in ['--decrypt', '-d']: key = -keydel sys.a(chǎn)rgv[index]breakif len(sys.a(chǎn)rgv) == 1: is_error = Trueelse:for arg in sys.a(chǎn)rgv:if arg.startswith('-'): is_error = Trueif is_error: print(f'Usage: python {sys.a(chǎn)rgv[0]} [ --key <key> ] [ --encrypt|decrypt ] <text>')else: print(encrypt(' '.join(sys.a(chǎn)rgv[1:]), key))if __name__ == '__main__': caesar()
這個(gè)腳本遵循了一些我們前面推薦的規(guī)則:
支持默認(rèn)秘鑰和默認(rèn)模式基本的錯(cuò)誤處理(沒有提供輸入文本的情況,以及提供了無法識別的參數(shù)的情況)出錯(cuò)時(shí)或者不帶任何參數(shù)調(diào)用腳本時(shí)會顯示文檔:> pythoncaesar_script_using_sys_argv.pyUsage: pythoncaesar.py[ --key <key> ][ --encrypt|decrypt ] <text>
但是,這個(gè)凱撒加密法腳本太長了(39 行,其中甚至還沒包括加密代碼本身),而且很難讀懂。
解析命令行參數(shù)應(yīng)該還有更好的辦法……
試試 argparse?
argparse 是 Python 用來解析命令行參數(shù)的標(biāo)準(zhǔn)庫。
我們來看看用 argparse 怎樣編寫凱撒加密的腳本:
import argparsefrom caesar_encryption import encryptdef caesar(): parser = argparse.ArgumentParser()group = parser.a(chǎn)dd_mutually_exclusive_group()group.a(chǎn)dd_argument('-e', '--encrypt', action='store_true')group.a(chǎn)dd_argument('-d', '--decrypt', action='store_true')parser.a(chǎn)dd_argument('text', nargs='*') parser.a(chǎn)dd_argument('-k', '--key', type=int, default=1) args = parser.parse_args() text_string = ' '.join(args.text)key = args.keyif args.decrypt: key = -key cyphertext = encrypt(text_string, key) print(cyphertext)if __name__ == '__main__': caesar()
這段代碼也遵循了上述規(guī)則,而且與前面的手工編寫的腳本相比,可以提供更準(zhǔn)確的文檔,以及更具有交互性的錯(cuò)誤處理:
> pythoncaesar_script_using_argparse.py--encodeMymessageusage: caesar_script_using_argparse.py[-h(huán)][-e | -d][-k KEY][text [text ...]]caesar_script_using_argparse.py: error: unrecognizedarguments: --encode> pythoncaesar_script_using_argparse.py--h(huán)elpusage: caesar_script_using_argparse.py[-h(huán)][-e | -d][-k KEY][text [text ...]]
positional arguments:textoptional arguments: -h(huán), --h(huán)elp show this help message andexit -e, --encrypt -d, --decrypt -k KEY, --keyKEY
但是,仔細(xì)看了這段代碼后,我發(fā)現(xiàn)(雖然有點(diǎn)主觀)函數(shù)開頭的幾行(從7行到13行)定義了參數(shù),但定義方式并不太優(yōu)雅:它太臃腫了,而且完全是程式化的。應(yīng)該有更描述性、更簡潔的方法。
click 能做得更好!
幸運(yùn)的是,有個(gè) Python 庫能提供與 argparse 同樣的功能(甚至還能提供更多),它的代碼風(fēng)格更優(yōu)雅。這個(gè)庫的名字叫 click。
這里是凱撒加密腳本的第三版,使用了 click:
import clickfrom caesar_encryption import encrypt@click.command()@click.a(chǎn)rgument('text', nargs=-1)@click.option('--decrypt/--encrypt', '-d/-e')@click.option('--key', '-k', default=1)def caesar(text, decrypt, key): text_string = ' '.join(text)if decrypt: key = -key cyphertext = encrypt(text_string, key) click.echo(cyphertext)if __name__ == '__main__':caesar()
注意現(xiàn)在參數(shù)和選項(xiàng)都在修飾器里定義,定義好的參數(shù)直接作為函數(shù)參數(shù)提供。
我來解釋一下上面代碼中的一些地方:
腳本參數(shù)定義中的nargs參數(shù)指定了該參數(shù)期待的單詞的數(shù)目(一個(gè)用引號括起來的字符串算一個(gè)單詞)。默認(rèn)值是1。這里nargs=-1允許接收任意數(shù)目的單詞。--encrypt/--decrypt這種寫法可以定義完全互斥的選項(xiàng)(類似于argparse中的add_mutually_exclusive_group函數(shù)),它將產(chǎn)生一個(gè)布爾型參數(shù)。click.echo是該庫提供的一個(gè)工具函數(shù),它的功能與print相同,但兼容Python 2和Python 3,還有一些其他功能(如處理顏色等)。
添加一些隱秘性
這個(gè)腳本的參數(shù)(被加密的消息)應(yīng)當(dāng)是最高機(jī)密。而我們卻要求用戶直接在終端里輸入文本,使得這些文本被記錄在命令歷史中,這不是很諷刺嗎?
解決方法之一就是使用隱藏的提示。或者可以從輸入文件中讀取文本,對于較長的文本來說更實(shí)際一些;蛘呖梢愿纱嘧層脩暨x擇。
輸出也一樣:用戶可以保存到文件中,也可以輸出到終端。這樣就得到了凱撒腳本的最后一個(gè)版本:
import clickfrom caesar_encryption import encrypt@click.command()@click.option('--input_file', type=click.File('r'),help='File in which there is the text you want to encrypt/decrypt.''If not provided, a prompt will allow you to type the input text.',)@click.option('--output_file', type=click.File('w'), help='File in which the encrypted / decrypted text will be written.''If not provided, the output text will just be printed.',)@click.option('--decrypt/--encrypt','-d/-e', help='Whether you want to encrypt the input text or decrypt it.')@click.option('--key','-k',default=1,help='The numeric key to use for the caesar encryption / decryption.')def caesar(input_file, output_file, decrypt, key):if input_file:text = input_file.read()else:text = click.prompt('Enter a text', hide_input=not decrypt)if decrypt:key = -key cyphertext = encrypt(text, key)if output_file:output_file.write(cyphertext)else: click.echo(cyphertext)if __name__ == '__main__': caesar()

最新活動更多
-
3月27日立即報(bào)名>> 【工程師系列】汽車電子技術(shù)在線大會
-
4月30日立即下載>> 【村田汽車】汽車E/E架構(gòu)革新中,新智能座艙挑戰(zhàn)的解決方案
-
5月15-17日立即預(yù)約>> 【線下巡回】2025年STM32峰會
-
即日-5.15立即報(bào)名>>> 【在線會議】安森美Hyperlux™ ID系列引領(lǐng)iToF技術(shù)革新
-
5月15日立即下載>> 【白皮書】精確和高效地表征3000V/20A功率器件應(yīng)用指南
-
5月16日立即參評 >> 【評選啟動】維科杯·OFweek 2025(第十屆)人工智能行業(yè)年度評選
推薦專題
- 1 UALink規(guī)范發(fā)布:挑戰(zhàn)英偉達(dá)AI統(tǒng)治的開始
- 2 “AI寒武紀(jì)”爆發(fā)至今,五類新物種登上歷史舞臺
- 3 降薪、加班、裁員三重暴擊,“AI四小龍”已折戟兩家
- 4 光計(jì)算迎來商業(yè)化突破,但落地仍需時(shí)間
- 5 大模型下半場:Agent時(shí)代為何更需要開源模型
- 6 中國“智造”背后的「關(guān)鍵力量」
- 7 優(yōu)必選:營收大增主靠小件,虧損繼續(xù)又逢關(guān)稅,能否乘機(jī)器人東風(fēng)翻身?
- 8 營收猛增46%,昆侖萬維成為AI“爆品工廠”
- 9 全球無人駕駛技術(shù)排名:誰才是細(xì)分賽道的扛把子?
- 10 地平線自動駕駛方案解讀