订阅
纠错
加入自媒体

如何编写完美的 Python命令行程序?

2019-01-21 08:40
来源: CSDN

作为 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 = 1is_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()

1  2  下一页>  
声明: 本文系OFweek根据授权转载自其它媒体或授权刊载,目的在于信息传递,并不代表本站赞同其观点和对其真实性负责,如有新闻稿件和图片作品的内容、版权以及其它问题的,请联系我们。

发表评论

0条评论,0人参与

请输入评论内容...

请输入评论/评论长度6~500个字

您提交的评论过于频繁,请输入验证码继续

暂无评论

暂无评论

文章纠错
x
*文字标题:
*纠错内容:
联系邮箱:
*验 证 码:

粤公网安备 44030502002758号