新聞中心
Python程序員的主要工作是寫(xiě)命令行程序,即直接在終端運(yùn)行的腳本。隨著項(xiàng)目規(guī)模增長(zhǎng),我們希望創(chuàng)建有效的命令行接口,通過(guò)提供不同的參數(shù),解決不同的問(wèn)題,而不是每次都修改源代碼。

網(wǎng)站建設(shè)哪家好,找成都創(chuàng)新互聯(lián)!專(zhuān)注于網(wǎng)頁(yè)設(shè)計(jì)、網(wǎng)站建設(shè)、微信開(kāi)發(fā)、微信小程序開(kāi)發(fā)、集團(tuán)企業(yè)網(wǎng)站建設(shè)等服務(wù)項(xiàng)目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了東風(fēng)免費(fèi)建站歡迎大家使用!
為了實(shí)現(xiàn)這一目標(biāo),我總結(jié)了四條原則,希望對(duì)大家有所幫助:
- 命令行參數(shù)應(yīng)提供默認(rèn)值
- 處理所有可能的參數(shù)錯(cuò)誤,包括缺少參數(shù),數(shù)據(jù)類(lèi)型錯(cuò)誤,無(wú)法找到文件等
- 撰寫(xiě)完善的文檔,解釋參數(shù)的含義以及如何設(shè)置
- 使用進(jìn)度條顯示長(zhǎng)時(shí)間運(yùn)行的任務(wù)
一個(gè)簡(jiǎn)單的例子
讓我們將這些規(guī)則應(yīng)用于一個(gè)具體的案例:一個(gè)使用Caesar cipher加密和解密消息的腳本。
假設(shè)我們編寫(xiě)了一個(gè)encrypt函數(shù),如下所示?,F(xiàn)在要?jiǎng)?chuàng)建一個(gè)的腳本來(lái)加密和解密消息。
腳本允許用戶選擇:模式(加密或解密),密鑰。前者的默認(rèn)值是加密,后者的默認(rèn)值是1。這一切都通過(guò)命令行參數(shù)實(shí)現(xiàn)。
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
初學(xué)者方法:sys.argv
腳本需要先獲取命令行參數(shù)的值,讓我們先用最簡(jiǎn)單的sys.argv實(shí)現(xiàn)。
sys.argv是一個(gè)列表,包含了用戶在運(yùn)行腳本時(shí)輸入的所有參數(shù)(包括腳本名字本身)。
在終端輸入以下指令:
> python caesar_script.py --key 23 --decrypt my secret message
pb vhfuhw phvvdjh
sys.argv列表包括:
['caesar_script.py', '--key', '23', '--decrypt', 'my', 'secret', 'message']
為了獲得參數(shù)值,需要遍歷參數(shù)列表,尋找一個(gè) '--key' (或 '-k' )來(lái)獲取密鑰值,并尋找一個(gè) '--decrypt' 獲取模式。
import sys
from caesar_encryption import encryp
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] [ --encrypt|decrypt ] ')
else:
print(encrypt(' '.join(sys.argv[1:]), key))
if __name__ == '__main__':
caesar()
代碼遵循我們一開(kāi)始提出的原則:
有一個(gè)默認(rèn)鍵值和一個(gè)默認(rèn)模式
處理基本錯(cuò)誤(不提供輸入文本或未知參數(shù))
在參數(shù)錯(cuò)誤或在不帶參數(shù)的情況下調(diào)用腳本時(shí),打印簡(jiǎn)潔的提示信息
> python caesar_script_using_sys_argv.py
Usage: python caesar.py [ --key] [ --encrypt|decrypt ]
但是這個(gè)版本的腳本相當(dāng)長(zhǎng)(39行,不包括加密函數(shù)),而且代碼非常丑陋。
是否有更好的方法來(lái)解析命令行參數(shù)?
進(jìn)入argparse
argparse是用于解析命令行參數(shù)的Python標(biāo)準(zhǔn)庫(kù)模塊。
修改腳本,使用argparse解析命令行參數(shù):
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()
代碼仍然遵守我們提出的原則,并且比手動(dòng)解析命令行參數(shù)提供更精確的文檔和更具交互性的錯(cuò)誤處理。
> 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行代碼定義了命令行參數(shù),但它們不是很優(yōu)雅:太冗長(zhǎng)且程序化,我們可以用更緊湊和聲明性的方式完成。
使用click創(chuàng)建更好的命令行接口
幸運(yùn)的是有一個(gè)三方庫(kù)click用于創(chuàng)建命令行接口,它不僅提供比argparse更多的功能, 而且代碼風(fēng)格更漂亮。用click替換argparse,繼續(xù)優(yōu)化腳本。
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()
注意,命令行參數(shù)和選項(xiàng)都在裝飾器中聲明, 這使得它們可以作為函數(shù)的參數(shù)直接訪問(wèn)。
讓我們仔細(xì)分析上面的代碼:
nargs定義了命令行參數(shù)接收的值的數(shù)量,默認(rèn)值為1,nargs=-1允許提供任意數(shù)量的單詞。
--encrypt/--decrypt定義互斥的選項(xiàng) ,最終以布爾值傳遞給程序。
click.echo是click庫(kù)提供的基礎(chǔ)函數(shù),功能類(lèi)似于print,但提供更強(qiáng)大的功能,例如調(diào)整打印到控制臺(tái)的文本的顏色。
從本地文件讀取輸入
命令行參數(shù)接收的值是將被加密的最高機(jī)密消息,所以如果要求用戶直接在終端中輸入純文本,可能會(huì)引發(fā)安全顧慮。
一種更安全的方法是使用隱藏提示,或者從本地文件讀取文本 ,這對(duì)于長(zhǎng)文本來(lái)說(shuō)更加實(shí)用。
這個(gè)想法同樣適用于輸出:用戶可以將其保存到文件中,或者在終端中打印出來(lái)。讓我們繼續(xù)優(yōu)化腳本。
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()
由于腳本變得更復(fù)雜,我們創(chuàng)建了參數(shù)文檔(通過(guò)定義click.option裝飾器的help參數(shù)實(shí)現(xiàn)),詳細(xì)解釋參數(shù)的功能,效果如下。
> 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.
我們有兩個(gè)新的參數(shù)input_file和output_file,類(lèi)型是click.File,click會(huì)用正確的模式打開(kāi)文件并處理可能發(fā)生的錯(cuò)誤。例如找不到文件:
> 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
如果未提供input_file,則我們用click.prompt,在命令行創(chuàng)建提示窗口,讓用戶直接輸入文本,該提示對(duì)于加密模式將是隱藏的。效果如下:
> python caesar_script_v2.py --encrypt --key 2
Enter a text: **************
yyy.ukectc.eqo
假設(shè)你是一名黑客:想要解密一個(gè)用凱撒加密過(guò)的密文,但你不知道秘鑰是什么。最簡(jiǎn)單的策略就是用所有可能的秘鑰調(diào)用解密函數(shù) 25 次,閱讀解密結(jié)果,看看哪個(gè)是合理的。但你很聰明,而且也很懶,所以你想讓整個(gè)過(guò)程自動(dòng)化。確定解密后的 25 個(gè)文本哪個(gè)最可能是原始文本的方法之一,就是統(tǒng)計(jì)所有這些文本中的英文單詞的個(gè)數(shù)。這可以使用 PyEnchant 模塊實(shí)現(xiàn):
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()
使用進(jìn)度條
示例中的文本包含10^4個(gè)單詞,因此該腳本需要大約5秒才能解密。這很正常,因?yàn)樗枰獧z查所有25個(gè)秘鑰,每個(gè)秘鑰都要檢查10^4個(gè)單詞是否出現(xiàn)在英文字典中。
假設(shè)你要解密的文本包括10^5個(gè)單詞,那么就要花費(fèi)50秒才能輸出結(jié)果,用戶可能會(huì)非常著急。因此我建議這種任務(wù)一定要顯示進(jìn)度條。特別是,顯示進(jìn)度條還非常容易實(shí)現(xiàn)。下面是個(gè)顯示進(jìn)度條的例子:
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庫(kù),tqdm.tqdm類(lèi)可以將任何可迭代對(duì)象轉(zhuǎn)化為一個(gè)進(jìn)度條。click也提供了類(lèi)似的接口來(lái)創(chuàng)建進(jìn)度條(click.progress_bar),但我覺(jué)得它不如tqdm好用。
網(wǎng)站欄目:使用click創(chuàng)建完美的Python命令行程序
本文鏈接:http://www.5511xx.com/article/cdeiijc.html


咨詢
建站咨詢
