如何編寫完美的 Python 命令行程序?

2020-12-12 CSDN

這篇文章將教你如何編寫完美的 Python 命令行程序,提高團隊的生產力,讓大家的工作更舒適。

作者 | Yannick Wolff

譯者 | 彎月

責編 | 屠敏

作為 Python 開發者,我們經常要編寫命令行程序。比如在我的數據科學項目中,我要從命令行運行腳本來訓練模型,以及計算算法的準確率等。

因此,更方便更易用的腳本能夠很好地提高生產力,特別是在有多個開發者從事同一個項目的場合下。

因此,我建議你遵循以下四條規則:

儘可能提供默認參數值所有錯誤情況必須處理(例如,參數缺失,類型錯誤,找不到文件)所有參數和選項必須有文檔不是立即完成的任務應當顯示進度條

舉個簡單的例子

我們把這些規則應用到一個具體的例子上。這個腳本可以使用凱撒加密法加密和解密消息。

假設已經有個寫好的 encrypt 函數(實現如下),我們需要創建一個簡單的腳本,用來加密和解密消息。我們希望讓用戶通過命令行參數選擇加密模式(默認)和解密模式,並選擇一個秘鑰(默認為 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

我們的腳本需要做的第一件事就是獲取命令行參數的值。當我搜索「python command line arguments」時,出現的第一個結果是關於sys.argv的,所以我們來試試這個方法……

「初學者」的方法

sys.argv 是個列表,包含用戶在運行腳本時輸入的所有參數(包括腳本名自身)。

例如,如果我輸入:

> pythoncaesar_script.py--key 23 --decryptmysecretmessagepbvhfuhwphvvdjh

該列表將包含:

['caesar_script.py', '--key', '23', '--decrypt', 'my', 'secret', 'message']

因此只需遍歷該參數列表,找到'--key'(或'-k')以得到秘鑰值,找到'--decrypt'以設置解密模式(實際上只需要使用秘鑰的反轉作為秘鑰即可)。

最後我們的腳本大致如下:

import sysfrom caesar_encryption import encryptdefcaesar(): key = 1 is_error = Falsefor index, arg in enumerate(sys.argv):if arg in ['--key', '-k'] and len(sys.argv) > index + 1: key = int(sys.argv[index + 1])del sys.argv[index]del sys.argv[index]breakfor index, arg in enumerate(sys.argv):if arg in ['--encrypt', '-e']:del sys.argv[index]breakif arg in ['--decrypt', '-d']: key = -keydel sys.argv[index]breakif len(sys.argv) == 1: is_error = Trueelse:for arg in sys.argv:if arg.startswith('-'): is_error = Trueif is_error: print(f'Usage: python {sys.argv[0]} [ --key <key> ] [ --encrypt|decrypt ] <text>')else: print(encrypt(' '.join(sys.argv[1:]), key))if __name__ == '__main__': caesar()

這個腳本遵循了一些我們前面推薦的規則:

支持默認秘鑰和默認模式基本的錯誤處理(沒有提供輸入文本的情況,以及提供了無法識別的參數的情況)出錯時或者不帶任何參數調用腳本時會顯示文檔:> pythoncaesar_script_using_sys_argv.pyUsage: pythoncaesar.py[ --key <key> ][ --encrypt|decrypt ] <text>

但是,這個凱撒加密法腳本太長了(39 行,其中甚至還沒包括加密代碼本身),而且很難讀懂。

解析命令行參數應該還有更好的辦法……

試試 argparse?

argparse 是 Python 用來解析命令行參數的標準庫。

我們來看看用 argparse 怎樣編寫凱撒加密的腳本:

import argparsefrom caesar_encryption import encryptdef caesar(): parser = argparse.ArgumentParser()group = parser.add_mutually_exclusive_group()group.add_argument('-e', '--encrypt', action='store_true')group.add_argument('-d', '--decrypt', action='store_true') parser.add_argument('text', nargs='*') parser.add_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()

這段代碼也遵循了上述規則,而且與前面的手工編寫的腳本相比,可以提供更準確的文檔,以及更具有交互性的錯誤處理:

> pythoncaesar_script_using_argparse.py--encodeMymessageusage: caesar_script_using_argparse.py[-h][-e | -d][-k KEY][text [text ...]]caesar_script_using_argparse.py: error: unrecognizedarguments: --encode> pythoncaesar_script_using_argparse.py--helpusage: caesar_script_using_argparse.py[-h][-e | -d][-k KEY][text [text ...]]

positional arguments:textoptional arguments: -h, --help show this help message andexit -e, --encrypt -d, --decrypt -k KEY, --keyKEY

但是,仔細看了這段代碼後,我發現(雖然有點主觀)函數開頭的幾行(從7行到13行)定義了參數,但定義方式並不太優雅:它太臃腫了,而且完全是程式化的。應該有更描述性、更簡潔的方法。

click 能做得更好!

幸運的是,有個 Python 庫能提供與 argparse 同樣的功能(甚至還能提供更多),它的代碼風格更優雅。這個庫的名字叫 click。

這裡是凱撒加密腳本的第三版,使用了 click:

import clickfrom caesar_encryption import encrypt@click.command()@click.argument('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()

注意現在參數和選項都在修飾器裡定義,定義好的參數直接作為函數參數提供。

我來解釋一下上面代碼中的一些地方:

腳本參數定義中的nargs參數指定了該參數期待的單詞的數目(一個用引號括起來的字符串算一個單詞)。默認值是1。這裡nargs=-1允許接收任意數目的單詞。--encrypt/--decrypt這種寫法可以定義完全互斥的選項(類似於argparse中的add_mutually_exclusive_group函數),它將產生一個布爾型參數。click.echo是該庫提供的一個工具函數,它的功能與print相同,但兼容Python 2和Python 3,還有一些其他功能(如處理顏色等)。

添加一些隱秘性

這個腳本的參數(被加密的消息)應當是最高機密。而我們卻要求用戶直接在終端裡輸入文本,使得這些文本被記錄在命令歷史中,這不是很諷刺嗎?

解決方法之一就是使用隱藏的提示。或者可以從輸入文件中讀取文本,對於較長的文本來說更實際一些。或者可以乾脆讓用戶選擇。

輸出也一樣:用戶可以保存到文件中,也可以輸出到終端。這樣就得到了凱撒腳本的最後一個版本:

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()

這個版本有什麼新東西嗎?

首先,注意到我給每個參數選項都加了個help參數。由於腳本變得複雜了,help參數可以給腳本的行為添加一些文檔。運行結果如下:> python caesar_script_v2.py --helpUsage: caesar_script_v2.py [OPTIONS]Options: --input_file FILENAME File in which there is the text you want to encrypt/decrypt. Ifnot provided, a prompt will allow you to type the input text. --output_file FILENAME File in which the encrypted/decrypted text will be written. Ifnot provided, the output text will just be printed. -d, --decrypt / -e, --encrypt Whether you want to encrypt the input textor decrypt it. -k, --keyINTEGER The numeric keyto use for the caesar encryption / decryption. --help Show this message andexit.

兩個新的參數:input_file 和 output_file,類型均為 click.File。該庫能夠用正確的模式打開文件,處理可能的錯誤,再執行函數。例如:> python caesar_script_v2.py --decrypt --input_file wrong_file.txtUsage: caesar_script_v2.py [OPTIONS]Error: Invalid value for"--input_file": Could notopen file: wrong_file.txt: No such file or directory

正像help文本中解釋的那樣,如果沒有提供input_file,就使用click.promp讓用戶直接在提示符下輸入文本,在加密模式下這些文本是隱藏的。如下所示:> python caesar_script_v2.py --encrypt --key 2Enter a text: **************yyy.ukectc.eqo

破解密文!

現在設想你是個黑客:你要解密一個用凱撒加密過的密文,但你不知道秘鑰是什麼。

最簡單的策略就是用所有可能的秘鑰調用解密函數 25 次,閱讀解密結果,看看哪個是合理的。

但你很聰明,而且也很懶,所以你想讓整個過程自動化。確定解密後的 25 個文本哪個最可能是原始文本的方法之一,就是統計所有這些文本中的英文單詞的個數。這可以使用 PyEnchant 模塊實現:

import clickimport enchantfrom caesar_encryption import encrypt@click.command()@click.option('--input_file', type=click.File('r'), required=True,)@click.option('--output_file', type=click.File('w'), required=True,)defcaesar_breaker(input_file, output_file): cyphertext = input_file.read() english_dictionnary = enchant.Dict("en_US") max_number_of_english_words = 0for key in range(26): plaintext = encrypt(cyphertext, -key) number_of_english_words = 0for word in plaintext.split(' '):if word and english_dictionnary.check(word): number_of_english_words += 1if number_of_english_words > max_number_of_english_words: max_number_of_english_words = number_of_english_words best_plaintext = plaintext best_key = key click.echo(f'The most likely encryption key is {best_key}. It gives the following plaintext:\n\n{best_plaintext[:1000]}...') output_file.write(best_plaintext)if __name__ == '__main__': caesar_breaker()

貌似運行得很不錯,但別忘了,好的命令行程序還有個規則需要遵守:

4.A 不是立即完成的任務應當顯示進度條。

示例中的文本包含10^4個單詞,因此該腳本需要大約5秒才能解密。這很正常,因為它需要檢查所有25個秘鑰,每個秘鑰都要檢查10^4個單詞是否出現在英文字典中。

假設你要解密的文本包括10^5個但IC,那麼就要花費50秒才能輸出結果,用戶可能會非常著急。

因此我建議這種任務一定要顯示進度條。特別是,顯示進度條還非常容易實現。

下面是個顯示進度條的例子:

import clickimport enchantfrom tqdm import tqdmfrom caesar_encryption import encrypt@click.command()@click.option('--input_file', type=click.File('r'), required=True,)@click.option('--output_file', type=click.File('w'), required=True,)defcaesar_breaker(input_file, output_file): cyphertext = input_file.read() english_dictionnary = enchant.Dict("en_US") best_number_of_english_words = 0for key in tqdm(range(26)): plaintext = encrypt(cyphertext, -key) number_of_english_words = 0for word in plaintext.split(' '):if word and english_dictionnary.check(word): number_of_english_words += 1if number_of_english_words > best_number_of_english_words: best_number_of_english_words = number_of_english_words best_plaintext = plaintext best_key = key click.echo(f'The most likely encryption key is {best_key}. It gives the following plaintext:\n\n{best_plaintext[:1000]}...') output_file.write(best_plaintext)if __name__ == '__main__': caesar_breaker()

你發現區別了嗎?可能不太好找,因為區別真的很小,只有四個字母:tqdm。

tqdm 是 Python 庫的名字,也是它包含的類的名字。只需用它包裹一個可迭代的東西,就能顯示出進度條:

forkeyin tqdm(range(26)):

這樣就能顯示出非常漂亮的進度條。我都不敢相信這是真的。

另外,click也提供類似的顯示進度條的工具(click.progress_bar),但我覺得它的外觀不太容易懂,而且要寫的代碼也多一些。

我希望這篇文章能讓你在改進開發者的體驗上多花點時間。

原文:https://blog.sicara.com/perfect-python-command-line-interfaces-7d5d4efad6a2作者:Yannick Wolff,Sicara 的數據科學家。本文為 CSDN 翻譯,如需轉載,請註明來源出處。

相關焦點

  • 如何編寫和運行Python程序
    本篇介紹在Windows、Linux、Mac OS不同環境下如何編寫和運行Pyhton程序。通過本篇的學習,可以達成如下目標。1、使用Pyhton自身提供的交互式解釋器在Linux、Windows、Mac OS的命令行窗口或Shell窗口,執行python命令,啟動Python交互式解釋器。
  • 謹記四條規則,便可寫出完美的Python命令行程序
    作為 Python 開發者,我們經常要編寫命令行程序。比如在我的數據科學項目中,我要從命令行運行腳本來訓練模型,以及計算算法的準確率等。本文引用地址:http://www.eepw.com.cn/article/201901/397090.htm  因此,更方便更易用的腳本能夠很好地提高生產力,特別是在有多個開發者從事同一個項目的場合下。
  • 讓你如紳士般基於描述編寫 Python 命令行工具的開源項目:docopt
    需要先設置解析器,再定義參數,再解析命令行,最後實現業務邏輯。而今天要介紹的 docopt[1]則是站在一個全新的視角來審視命令行。你可曾想過,一個命令行程序的幫助信息其實已然包含了這個命令行的完整元信息,那麼是否可以通過定義幫助信息來定義命令行呢?docopt 就是基於這樣的想法去設計的。本系列文章默認使用 Python 3 作為解釋器進行講解。
  • 在Python中創建命令行界面的優秀方式
    我們先給大家介紹什麼是命令行界面(CLI): 命令行界面或命令語言解釋器,也稱為命令行用戶界面、控制臺用戶界面和字符用戶界面,是一種與電腦程式交互的方式,用戶以連續的文本行形式向程序發出命令。
  • 輕鬆編寫命令行接口,argparse模塊你值得擁有!
    圖源:unsplash相信大多數人都會用這行命令運行python腳本。$ python main.py我們能否對該腳本稍作修改比如說定義自己的參數?當然可以!$ python main.py arg1 arg2Python中的argparse模塊能解決這個問題。argparse 模塊可以讓人輕鬆編寫用戶友好的命令行接口。程序定義它需要的參數,然後argparse將弄清如何從sys.argv解析出那些參數。argparse模塊還會自動生成幫助和使用手冊,並在用戶給程序傳入無效參數時報出錯誤信息。
  • Python 命令行之旅:使用 docopt 實現 git 命令
    三、關於 gitpython gitpython[2]是一個和 git 倉庫交互的 Python 第三方庫。我們將借用它的能力來實現真正的 git 邏輯。安裝:pip install gitpython四、思考 在實現前,我們不妨先思考下會用到 docopt 的哪些功能?整個程序的結構是怎樣的?
  • @Python 開發者,如何更加高效地編寫代碼?
    對於 Python 開發者而言,Anaconda 能省下大量時間下載和安裝模塊包、處理項目環境等問題,幫助開發者更加愉快地編寫代碼。如果你苦於給 Python 安裝各種包,安裝過程中還各種出錯。那麼我牆裂推薦——Anaconda,它可以幫助你管理這些包,包括安裝、卸載、更新。
  • Python小知識:如何編寫一個本地論文管理器?「西安IT培訓」
    西安IT培訓小編給大家講解下Python小知識,如何編寫一個本地論文管理器? 2、實現實現這裡不想講太多,主要是設計程序的思路,原始碼在文末給出,都有注釋。首先是圖形化界面和命令行的選擇,最終選擇了命令行,開發速度更快,使用起來更直接。命令行的實現使用python自帶的cmd模塊實現。
  • Python 命令行之旅:深入 argparse(二)
    劇照 | 《仙劍奇俠傳》前言在上一篇「深入 argparse (一)」的文章中,我們深入了解了 argparse 的包括參數動作和參數類別在內的基本功能,具備了編寫一個簡單命令行程序的能力。本文將繼續深入了解 argparse 的進階玩法,一窺探其全貌,助力我們擁有實現複雜命令行程序的能力。
  • 慢步python,如何用python語言創造出一個真正的獨立exe程序?
    我們學習編程,終極目標還是編寫一個獨立的應用程式。獨立的應用程式應該像QQ,微信一樣不依靠其他程序運行,只有平臺支持,就可以運行。從這個意義上說,所用應用程式都是基於作業系統運行的。那麼如何用python語言創造出一個真正的獨立的應用程式?就是我們電腦上的exe程序?
  • Python程序的編輯及運行,Pycharm的下載安裝
    學編程,最重要還是自己動手,在動手編寫和調試過程中,你才知道,握草,這我都沒想到,或者,我去,這個低級錯誤我也犯!Python程序的運行方式:在我這有三種:1、通過命令行(command.exe)運行Python。
  • Python—程序語言入門
    >3、Python可以用來做什麼   開發遊戲&軟體(遊戲後臺大多數用python編寫);   製作更高級的Flash(web flash game);   創建資料庫;黑客hacker(遠程攻擊計算機、破解硬體or軟體);發明變形金剛(2006年日本FANUC推出的人工智慧機器人);學習其他語言的基礎(所有程序語言都有共同性
  • Python 命令行參數解析庫argparse
    output_source_dir,feature,levels,但是該函數print不能在命令行運行,只能在腳本內部調用,於是我們需要python標準庫內的argparse[1]來幫忙。2步, 通過help parser ,顯示其參數有:- pro--程序名稱,默認為 sys.argv[0]-usage--用法信息,默認從參數自動獲取-description-- 描述項目是做什麼的-epilog--緊隨著參數描述(argument)的文本-formatter_class--自定義幫助信息的格式-prefix_chars-- 命令行的前綴- fromfile_prefix_chars
  • Debian中編寫你的第一個Apple Swift程序
    據開發人員聲稱,Swift是一種編寫軟體的出色方法,無論面向手機、臺式機、伺服器還是運行代碼的其他系統。它是一種安全快速的交互式程式語言,結合了現代語言思維的優點、更廣泛的蘋果工程文化的智慧以及其開源社區的各種貢獻。編譯器針對性能進行了優化,而語言針對開發進行了優化,不犧牲任何一方面。本文將介紹如何在Debian上安裝最新版本的Swift。
  • 計算機畢業設計中用HTML網頁調用本地Python程序
    一、編寫你的python代碼我們就先寫一個簡單的吧(這個能讓你很容易地看到效果),命名為test2.py,寫入代碼:import osfile = open('new_file' + '.txt','w')file.close()注意:這一步在測試1中不用 二、編寫你的HTML
  • python執行系統命令
    在實際開發中,除了編寫python自身的代碼外,還經常需要執行作業系統的命令。
  • Python黑客學習筆記:從HelloWorld到編寫PoC(上)
    打開終端,執行'python'命令打開Python解釋器:~$ pythonPython 2.7.3Type "help", "copyright", "credits" or "license" for more information.
  • 給你Python程序構建一個優雅的終端CLI界面
    我們知道在Linux下有優雅的shell終端命令行界面,shell腳本都可以優雅用命令行的方式來運行。而且shell也再帶優化命令行參數解析的bash內部命令getopts和大多數發行版附帶的外部命令getops。Perl語言也有Getopt::XX系列模塊來實現類似功能;Golang也有flag標準庫以及更加強大的第三庫cobra。
  • 如何建立一個完美的 Python 項目?
    在理想世界中,所有開發人員的關係是相互依賴和關聯的(協作開發),代碼要有完美的格式、沒有低級的錯誤、並且測試覆蓋了所有代碼。另外,所有這些將在每次提交時都可以得到保證。(代碼風格統一、類型檢測、測試覆蓋率高、自動檢測)在本文中,我將介紹如何建立一個可以做到這些點的項目。
  • 如何編寫簡潔美觀的Python代碼
    編寫代碼是數據科學家或分析師角色的一部分。另一方面,編寫漂亮整潔的Python代碼完全是另一回事。作為一個精通分析或數據科學領域(甚至軟體開發)的程式設計師,這很可能會改變你的形象。那麼,我們如何編寫這種所謂漂亮的Python代碼呢?