来源:好完美 浏览次数: 发布时间:2023-05-31 18:24:49
Python 程序员的主要工作是编写命令行程序,即直接在终端中运行的脚本。 随着项目规模的增长,我们希望创建一个有效的命令行界面,通过提供不同的参数来解决不同的问题,而不是每次都修改源代码。
为了实现这个目标,我总结了四个原则,希望对大家有所帮助:
一个简单的例子
让我们将这些规则应用到一个具体案例中:一个使用凯撒密码加密和解密消息的脚本。
假设我们编写了一个加密函数,如下所示。 现在创建一个脚本来加密和解密消息。
该脚本允许用户选择:模式(加密或解密)、密钥。 前者的默认值为加密,后者的默认值为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
登录复制
初学者方法:sys.argv
脚本需要先获取命令行参数的值,我们先用最简单的sys.argv来实现。
sys.argv 是一个列表,包含用户在运行脚本时输入的所有参数(包括脚本名称本身)。
在终端中输入以下命令:
> python caesar_script.py --key 23 --decrypt my secret message pb vhfuhw phvvdjh
登录复制
sys.argv 列表包括:
['caesar_script.py', '--key', '23', '--decrypt', 'my', 'secret', 'message']
登录复制
要获取参数值,请遍历参数列表,查找“--key”(或“-k”)以获取密钥值,并查找“--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()
登录复制
代码遵循我们一开始提出的原则:
具有默认键值和默认架构
处理基本错误(未提供输入文本或未知参数)
参数错误或无参数调用脚本时打印简洁信息
> python caesar_script_using_sys_argv.py Usage: python caesar.py [ --key] [ --encrypt|decrypt ]
登录复制
但是这个版本的脚本比较长(39行,不包括加密功能),而且代码非常难看。
有没有更好的方法来解析命令行参数?
进入 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 行定义了命令行参数,但它们不是很优雅:过于冗长和过程化,我们可以用更紧凑和声明的方式来完成。
通过单击创建更好的命令行界面
幸运的是,有一个创建命令行界面的三方库click,它不仅提供了比argparse更多的功能,而且代码风格也更漂亮。 将argparse换成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 定义最终作为布尔值传递给程序的互斥选项。
click.echo 是click库提供的基础函数。 它的功能和print类似,但是提供了更强大的功能,比如调整打印到控制台的文本的颜色。
从本地文件读取输入
命令行参数接收的值是将被加密的绝密消息,因此如果要求用户直接在终端中输入纯文本,可能会引起安全问题。
更安全的方法是使用隐藏提示完美世界改代码步骤,或者从本地文件中读取文本,这对于长文本更实用。
同样的想法也适用于输出:用户可以将其保存到文件中,或者在终端中打印出来。 让我们继续优化脚本。
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()
登录复制
随着脚本越来越复杂,我们创建了一个参数文档(通过定义click.option装饰器的help参数)来详细解释参数的作用,效果如下。
> python caesar_script_v2.py --help Usage: caesar_script_v2.py [OPTIONS] Options: --input_file FILENAMEFile 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, --encryptWhether you want to encrypt the input text or decrypt it. -k, --key INTEGERThe numeric key to use for the caesar encryption / decryption. --help Show this message and exit.
登录复制
我们有两个新参数 input_file 和 output_file完美世界改代码步骤,类型为 click.File,click 将以正确的模式打开文件并处理可能的错误。 例如找不到文件:
> 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在命令行创建一个提示窗口,让用户直接输入文本,加密模式会隐藏提示。 效果如下:
> python caesar_script_v2.py --encrypt --key 2 Enter a text: ************** yyy.ukectc.eqo
登录复制
假设你是一名黑客:你想解密一段用凯撒加密的密文,但你不知道密钥是什么。 最简单的策略是使用所有可能的密钥调用解密函数 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:nn{best_plaintext[:1000]}...') output_file.write(best_plaintext) if __name__ == '__main__': caesar_breaker()
登录复制
使用进度条
示例中的文本包含 10^4 个单词,因此脚本解密大约需要 5 秒。 这是正常的,因为它需要检查所有 25 个键,每个键检查 10^4 个单词是否出现在英语词典中。
假设你要解密的文本包括10^5个单词,输出结果需要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:nn{best_plaintext[:1000]}...') output_file.write(best_plaintext) if __name__ == '__main__': caesar_breaker()
登录复制
这里使用了tqdm库,tqdm.tqdm类可以将任何可迭代对象转换成进度条。 click也提供了一个类似的界面来创建进度条(click.progress_bar),但我认为它不如tqdm好用。
以上就是使用click创建一个完美的Python命令行程序的详细内容。 更多内容请关注php中文网其他相关文章!