怎樣進入編程命令行,如何編寫完美的 Python 命令行程序?

 2023-10-22 阅读 14 评论 0

摘要:這篇文章將教你如何編寫完美的 Python 命令行程序,提高團隊的生產力,讓大家的工作更舒適。作者 |?Yannick Wolff譯者 |?彎月責編 | 屠敏出品 |?CSDN(ID:CSDNNews)作為 Python 開發者,我們經常要編寫命令行程序。比如在我的數據科學

640?wx_fmt=gif

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

640?wx_fmt=jpeg

作者 |?Yannick Wolff

譯者 |?彎月

責編 | 屠敏

出品 |?CSDN(ID:CSDNNews)

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

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

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

  1. 盡可能提供默認參數值

  2. 所有錯誤情況必須處理(例如,參數缺失,類型錯誤,找不到文件)

  3. 所有參數和選項必須有文檔

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


640?wx_fmt=png

舉個簡單的例子


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

假設已經有個寫好的 encrypt 函數(實現如下),我們需要創建一個簡單的腳本,用來加密和解密消息。我們希望讓用戶通過命令行參數選擇加密模式(默認)和解密模式,并選擇一個秘鑰(默認為 1)。命令行打開進程,

def?encrypt(plaintext,?key):
????cyphertext?=?''
????for?character?in?plaintext:
????????if?character.isalpha():
????????????number?=?ord(character)
????????????number?+=?key
????????????if?character.isupper():
????????????????if?number?>?ord('Z'):
????????????????????number?-=?26
????????????????elif?number?<?ord('A'):
????????????????????number?+=?26
????????????elif?character.islower():
????????????????if?number?>?ord('z'):
????????????????????number?-=?26
????????????????elif?number?<?ord('a'):
????????????????????number?+=?26
????????????character?=?chr(number)
????????cyphertext?+=?character

????return?cyphertext


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


640?wx_fmt=png

“初學者”的方法


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

例如,如果我輸入:

>?python?caesar_script.py?--key?23?--decrypt?my?secret?message
pb?vhfuhw?phvvdjh


該列表將包含:

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


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

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

import?sys

from?caesar_encryption?import?encrypt


def?caesar():
????key?=?1
????is_error?=?False

????for?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]
????????????break

????for?index,?arg?in?enumerate(sys.argv):
????????if?arg?in?['--encrypt',?'-e']:
????????????del?sys.argv[index]
????????????break
????????if?arg?in?['--decrypt',?'-d']:
????????????key?=?-key
????????????del?sys.argv[index]
????????????break

????if?len(sys.argv)?==?1:
????????is_error?=?True
????else:
????????for?arg?in?sys.argv:
????????????if?arg.startswith('-'):
????????????????is_error?=?True

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


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

  1. 支持默認秘鑰和默認模式

  2. 基本的錯誤處理(沒有提供輸入文本的情況,以及提供了無法識別的參數的情況)

  3. 出錯時或者不帶任何參數調用腳本時會顯示文檔:

>?python?caesar_script_using_sys_argv.py
Usage:?python?caesar.py?[?--key?<key>?]?[?--encrypt|decrypt?]?<text>

但是,這個凱撒加密法腳本太長了(39 行,其中甚至還沒包括加密代碼本身),而且很難讀懂。匯編語言編寫的程序通常?

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


640?wx_fmt=png

試試 argparse?


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

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

import?argparse

from?caesar_encryption?import?encrypt


def?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.key
????if?args.decrypt:
????????key?=?-key
????cyphertext?=?encrypt(text_string,?key)
????print(cyphertext)

if?__name__?==?'__main__':
????caesar()


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

>?python?caesar_script_using_argparse.py?--encode?My?message

usage:?caesar_script_using_argparse.py?[-h]?[-e?|?-d]?[-k?KEY]?[text?[text?...]]
caesar_script_using_argparse.py:?error:?unrecognized?arguments:?--encode
>?python?caesar_script_using_argparse.py?--help

usage:?caesar_script_using_argparse.py?[-h]?[-e?|?-d]?[-k?KEY]?[text?[text?...]]


positional?arguments:
??text
optional?arguments:
??-h,?--help?????????show?this?help?message?and?exit
??-e,?--encrypt
??-d,?--decrypt
??-k?KEY,?--key?KEY


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


640?wx_fmt=png

click 能做得更好!


幸運的是,有個 Python 庫能提供與 argparse 同樣的功能(甚至還能提供更多),它的代碼風格更優雅。用高級程序設計語言編寫的程序?這個庫的名字叫 click。

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

import?click

from?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,還有一些其他功能(如處理顏色等)。


640?wx_fmt=png

添加一些隱秘性


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

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

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

import?click

from?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?--help
Usage:?caesar_script_v2.py?[OPTIONS]
Options:
??--input_file?FILENAME??????????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.
??--output_file?FILENAME?????????File?in?which?the?encrypted/decrypted?text?will?be?written.?If?not?provided,?the?output?text?will?just?be?printed.
??-d,?--decrypt?/?-e,?--encrypt??Whether?you?want?to?encrypt?the?input?text?or?decrypt?it.
??-k,?--key?INTEGER??????????????The?numeric?key?to?use?for?the?caesar?encryption?/?decryption.
??--help?????????????????????????Show?this?message?and?exit.
  • 兩個新的參數:input_file 和 output_file,類型均為 click.File。該庫能夠用正確的模式打開文件,處理可能的錯誤,再執行函數。例如:

>?python?caesar_script_v2.py?--decrypt?--input_file?wrong_file.txt
Usage:?caesar_script_v2.py?[OPTIONS]
Error:?Invalid?value?for?"--input_file":?Could?not?open?file:?wrong_file.txt:?No?such?file?or?directory
  • 正像help文本中解釋的那樣,如果沒有提供input_file,就使用click.promp讓用戶直接在提示符下輸入文本,在加密模式下這些文本是隱藏的。如下所示:

>?python?caesar_script_v2.py?--encrypt?--key?2
Enter?a?text:?**************
yyy.ukectc.eqo


640?wx_fmt=png

破解密文!


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

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

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

import?click
import?enchant

from?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,
)
def?caesar_breaker(input_file,?output_file):
????cyphertext?=?input_file.read()
????english_dictionnary?=?enchant.Dict("en_US")
????max_number_of_english_words?=?0
????for?key?in?range(26):
????????plaintext?=?encrypt(cyphertext,?-key)
????????number_of_english_words?=?0
????????for?word?in?plaintext.split('?'):
????????????if?word?and?english_dictionnary.check(word):
????????????????number_of_english_words?+=?1
????????if?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()

640?wx_fmt=gif

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

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

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

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

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

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

import?click
import?enchant

from?tqdm?import?tqdm

from?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,
)
def?caesar_breaker(input_file,?output_file):
????cyphertext?=?input_file.read()
????english_dictionnary?=?enchant.Dict("en_US")
????best_number_of_english_words?=?0
????for?key?in?tqdm(range(26)):
????????plaintext?=?encrypt(cyphertext,?-key)
????????number_of_english_words?=?0
????????for?word?in?plaintext.split('?'):
????????????if?word?and?english_dictionnary.check(word):
????????????????number_of_english_words?+=?1
????????if?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 庫的名字,也是它包含的類的名字。只需用它包裹一個可迭代的東西,就能顯示出進度條:

for?key?in?tqdm(range(26)):

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

640?wx_fmt=png

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

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

原文:https://blog.sicara.com/perfect-python-command-line-interfaces-7d5d4efad6a2

作者:Yannick Wolff,Sicara 的數據科學家。

本文為?CSDN 翻譯,如需轉載,請注明來源出處。

?熱 文?推 薦?

??MongoDB 涼了?

??和 C++ 相比,我為什么要選擇 Rust 來開發軟件?

??如何設計一個實用的線程池?

?“對不起,你的離職是我的錯!”

??Gartner的預言:通向混合IT之旅

??阿里“菜鳥”AI?

??剛剛!華為又被美國盯上了!

??心疼!能為程序員男友做些什么嗎?

640?wx_fmt=gif

print_r('點個好看吧!');
var_dump('點個好看吧!');
NSLog(@"點個好看吧!");
System.out.println("點個好看吧!");
console.log("點個好看吧!");
print("點個好看吧!");
printf("點個好看吧!");
cout?<<?"點個好看吧!"?<<?endl;
Console.WriteLine("點個好看吧!");
fmt.Println("點個好看吧!");
Response.Write("點個好看吧!");
alert("點個好看吧!")
echo "點個好看吧!"

640?wx_fmt=gif點擊“閱讀原文”,打開 CSDN App 閱讀更貼心!

640?wx_fmt=png喜歡就點擊“好看”吧


版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/3/163280.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息