技术流 | 手把手教你用Python设计一个命令行界面
作者 | Yannick Wolff
译者 | 刘旭坤
整理 | Jane
出品 | Python大本营
对 Python 程序来说,完备的命令行界面可以提升团队的工作效率,减少调用时可能碰到的困扰。今天,我们就来教大家如何设计功能完整的 Python 命令行界面。
对 Python 开发者来说用的最多的界面恐怕还是命令行。就拿我参与的机器学习项目来说,训练模型和评估算法的精确度都是通过在命令行界面运行脚本来完成的。
所以调用一个 Python 脚本的时候我们希望这段脚本有一个尽量简洁方便调用的接口。尤其是团队中有多名开发者的时候这一点对提升团队的工作效率很重要。
要让一段脚本方便调用总的来说有四个原则需要遵守:
提供默认参数值
处理调用出错的情况,比如缺少参数、参数类型错误或者找不到文件等
在文档中说明各个参数和选项的用法
如果执行时间较长应该提供进度条
一个简单的例子
下面我们先通过一个简单的例子来谈谈这四个原则的具体应用。例子中给出的脚本的功能是使用凯撒码变换对文本进行加密和解密。
Caesar cipher:一种简单的消息编码方式。在密码学中,凯撒密码,移位密码是最简单和最广为人知的加密技术之一。
比如说我们想让用户通过命令行参数来选择调用的方式是加密还是解密文本,而且用户要从命令行传入下面 encrypt 函数中的密匙参数 key。
1def encrypt(plaintext, key):
2 cyphertext = ''
3 for character in plaintext:
4 if character.isalpha():
5 number = ord(character)
6 number += key
7 if character.isupper():
8 if number > ord('Z'):
9 number -= 26
10 elif number < ord('A'):
11 number += 26
12 elif character.islower():
13 if number > ord('z'):
14 number -= 26
15 elif number < ord('a'):
16 number += 26
17 character = chr(number)
18 cyphertext += character
19
20 return cyphertext
首先我们得在程序中拿到命令行参数。我在网上搜“ python 命令行参数”出来的第一个结果说让我用 sys.argv ,那我们就来试试看它好不好用。
初级:笨办法
其实 sys.argv 只是一个 list ,这个 list 的内容是用户调用脚本时所输入的所有参数(其中也包括脚本的文件名)。
如果我像下面这样调用加解密的脚本 caesar_script.py 的话:
1> python caesar_script.py --key 23 --decrypt my secret message
2pb vhfuhw phvvdjh
sys.argv 这个 list 的值就是:
1['caesar_script.py', '--key', '23', '--decrypt', 'my', 'secret', 'message']
所以我们现在要遍历这个 list 来找其中是否包括了“ –key ”或者“ -k ”,这样我们就能找到密匙“ 23 ”。再找到“ –decrypt ”就能知道用户是想要解密一段文本了(其实解密就是用密匙的相反数再加密一次)。
完成后的代码如下:
1import sys
2
3from caesar_encryption import encrypt
4
5
6def caesar():
7 key = 1
8 is_error = False
9
10 for index, arg in enumerate(sys.argv):
11 if arg in ['--key', '-k'] and len(sys.argv) > index + 1:
12 key = int(sys.argv[index + 1])
13 del sys.argv[index]
14 del sys.argv[index]
15 break
16
17 for index, arg in enumerate(sys.argv):
18 if arg in ['--encrypt', '-e']:
19 del sys.argv[index]
20 break
21 if arg in ['--decrypt', '-d']:
22 key = -key
23 del sys.argv[index]
24 break
25
26 if len(sys.argv) == 1:
27 is_error = True
28 else:
29 for arg in sys.argv:
30 if arg.startswith('-'):
31 is_error = True
32
33 if is_error:
34 print(f'Usage: python {sys.argv[0]} [ --key <key> ] [ --encrypt|decrypt ] <text>')
35 else:
36 print(encrypt(' '.join(sys.argv[1:]), key))
37
38if __name__ == '__main__':
39 caesar()
这段代码基本上遵守了我们提到的四个原则:
key 和 加密模式都设置了缺省参数
脚本可以处理像没有文本或者缺少参数这样比较基本的错误
用户没有给参数或者有错的话会显示使用帮助
1> python caesar_script_using_sys_argv.py
2Usage: python caesar.py [ --key <key> ] [ --encrypt|decrypt ] <text>
然而不算加密函数光处理参数我们就已经写了 39 行而且写得一点也不优雅。我有胆说肯定还有更好的办法来读命令行参数。
中级:argparse
Python 标准库里面提供了一个读取命令行参数的库——argparse 。我们来看看如果用 argparse 代码怎么写:
1import argparse
2
3from caesar_encryption import encrypt
4
5
6def caesar():
7 parser = argparse.ArgumentParser()
8 group = parser.add_mutually_exclusive_group()
9 group.add_argument('-e', '--encrypt', action='store_true')
10 group.add_argument('-d', '--decrypt', action='store_true')
11 parser.add_argument('text', nargs='*')
12 parser.add_argument('-k', '--key', type=int, default=1)
13 args = parser.parse_args()
14
15 text_string = ' '.join(args.text)
16 key = args.key
17 if args.decrypt:
18 key = -key
19 cyphertext = encrypt(text_string, key)
20 print(cyphertext)
21
22if __name__ == '__main__':
23 caesar()
24view raw
这样写也符合四项指导原则,而且对参数的说明和错误处理都优于使用 sys.argv 的笨办法:
1> python caesar_script_using_argparse.py --encode My message
2
3usage: caesar_script_using_argparse.py [-h] [-e | -d] [-k KEY] [text [text ...]]
4caesar_script_using_argparse.py: error: unrecognized arguments: --encode
5> python caesar_script_using_argparse.py --help
6
7usage: caesar_script_using_argparse.py [-h] [-e | -d] [-k KEY] [text [text ...]]
8positional arguments:
9 text
10optional arguments:
11 -h, --help show this help message and exit
12 -e, --encrypt
13 -d, --decrypt
14 -k KEY, --key KEY
不过我个人还是觉得代码里第 7 行到第 13 行定义参数的部分写得很啰嗦,而且我觉得参数应该使用声明式的方法来定义。
高级: click
还有一个叫 click 的库能实现我们想要的这些。它的基本功能和 argparse 是一样的,但写出来的代码更优雅。
使用 click 改写我们的加解密脚本之后是这样的:
1import click
2
3from caesar_encryption import encrypt
4
5
6
7
8
9def caesar(text, decrypt, key):
10 text_string = ' '.join(text)
11 if decrypt:
12 key = -key
13 cyphertext = encrypt(text_string, key)
14 click.echo(cyphertext)
15
16if __name__ == '__main__':
17 caesar()
18view raw
我们需要的参数和选项都用装饰器来声明,这样就可以在 caesar 函数里直接使用了。
上面的代码里有几点需要说明:
nargs 参数是说这个参数的长度是几个词。默认值是 1 不过用引号引起来的句子也只算一个词。这里我们设为 -1 是指不限制长度。
--decrypt/--encrypt 这样加一个斜杠的写法用来指明互斥的选项,它的功能和 argparse 中的 add_mutually_exclusive_group 函数类似。
click.echo 是 click 提供的一个 print 功能,与 Python 2 和 3 都兼容,而且有颜色高亮功能。
添加隐私功能
我们写的是一个对文本加解密的脚本,但用户却直接把要加密的文本打出来了,这样有别人用这个命令行的话按几下上方向键就能看到我们的用户加密了什么东西,这是在是有点荒唐。
我们可以选择把用户要加密的文本隐藏起来,或者是从文件里读文本。这两种方法都能解决我们的问题,但选择权应该留给用户。
同理对于加解密的结果我们也让用户选择是直接在命令行输出还是保存成一个文件:
1import click
2
3from caesar_encryption import encrypt
4
5
6
7 '--input_file',
8 type=click.File('r'),
9 help='File in which there is the text you want to encrypt/decrypt.'
10 'If not provided, a prompt will allow you to type the input text.',
11)
12
13 '--output_file',
14 type=click.File('w'),
15 help='File in which the encrypted / decrypted text will be written.'
16 'If not provided, the output text will just be printed.',
17)
18
19 '--decrypt/--encrypt',
20 '-d/-e',
21 help='Whether you want to encrypt the input text or decrypt it.'
22)
23
24 '--key',
25 '-k',
26 default=1,
27 help='The numeric key to use for the caesar encryption / decryption.'
28)
29def caesar(input_file, output_file, decrypt, key):
30 if input_file:
31 text = input_file.read()
32 else:
33 text = click.prompt('Enter a text', hide_input=not decrypt)
34 if decrypt:
35 key = -key
36 cyphertext = encrypt(text, key)
37 if output_file:
38 output_file.write(cyphertext)
39 else:
40 click.echo(cyphertext)
41
42if __name__ == '__main__':
43 caesar()
44view raw
这里我给每个参数和选项都加上了一小段说明,这样我们的文档能更清楚一点因为我们现在参数有点多了。现在的文档是这样的:
1> python caesar_script_v2.py --help
2Usage: caesar_script_v2.py [OPTIONS]
3Options:
4 --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.
5 --output_file FILENAME File in which the encrypted/decrypted text will be written. If not provided, the output text will just be printed.
6 -d, --decrypt / -e, --encrypt Whether you want to encrypt the input text or decrypt it.
7 -k, --key INTEGER The numeric key to use for the caesar encryption / decryption.
8 --help Show this message and exit.
两个新的参数 input_file 和 output_file 都是 click.File 类型,而且 click 帮我们处理了文件打开的读写方式和可能出现的错误,比如这样:
1> python caesar_script_v2.py --decrypt --input_file wrong_file.txt
2Usage: caesar_script_v2.py [OPTIONS]
3Error: Invalid value for "--input_file": Could not open file: wrong_file.txt: No such file or directory
如果用户没有提供 input_file 的话,如说明文档中所写,则会让用户在命令行进行输入,而且用户输入不再是明文了:
1> python caesar_script_v2.py --encrypt --key 2
2Enter a text: **************
3yyy.ukectc.eqo
破译密码
假设我们现在是黑客,想解密但是不知道密匙该怎么办呢?对凯撒加密的英文来说很容易,只要调用解密函数 25 次然后看看那个结果不是乱码就行了。
要调用 25 次还要一个一个看还是太麻烦,其实只要数数哪个结果里正确的英文词最多就行了。下面我们就用 PyEnchant 来实现自动破译密码:
1import click
2import enchant
3
4from caesar_encryption import encrypt
5
6
7
8 '--input_file',
9 type=click.File('r'),
10 required=True,
11)
12
13 '--output_file',
14 type=click.File('w'),
15 required=True,
16)
17def caesar_breaker(input_file, output_file):
18 cyphertext = input_file.read()
19 english_dictionnary = enchant.Dict("en_US")
20 max_number_of_english_words = 0
21 for key in range(26):
22 plaintext = encrypt(cyphertext, -key)
23 number_of_english_words = 0
24 for word in plaintext.split(' '):
25 if word and english_dictionnary.check(word):
26 number_of_english_words += 1
27 if number_of_english_words > max_number_of_english_words:
28 max_number_of_english_words = number_of_english_words
29 best_plaintext = plaintext
30 best_key = key
31 click.echo(f'The most likely encryption key is {best_key}. It gives the following plaintext:\n\n{best_plaintext[:1000]}...')
32 output_file.write(best_plaintext)
33
34if __name__ == '__main__':
35 caesar_breaker()
36view raw
一气呵成!
不过我们好像还没有提到四项原则的最后一点:
4.如果执行时间较长应该提供进度条
上面的脚本破译 104 个词的文本大约需要 5 秒。考虑到要遍历 25 个密匙还要数英文词的个数这个时间并不算慢。
不过文本再长的话,比如 105 个词的文本,就要花 50 秒。这就有点长了,用户可能没有耐心等到程序运行完就强退了。
所以我建议如果执行时间长的话最好加上进度条,关键是写起来非常简单:
1import click
2import enchant
3
4from tqdm import tqdm
5
6from caesar_encryption import encrypt
7
8
9
10 '--input_file',
11 type=click.File('r'),
12 required=True,
13)
14
15 '--output_file',
16 type=click.File('w'),
17 required=True,
18)
19def caesar_breaker(input_file, output_file):
20 cyphertext = input_file.read()
21 english_dictionnary = enchant.Dict("en_US")
22 best_number_of_english_words = 0
23 for key in tqdm(range(26)):
24 plaintext = encrypt(cyphertext, -key)
25 number_of_english_words = 0
26 for word in plaintext.split(' '):
27 if word and english_dictionnary.check(word):
28 number_of_english_words += 1
29 if number_of_english_words > best_number_of_english_words:
30 best_number_of_english_words = number_of_english_words
31 best_plaintext = plaintext
32 best_key = key
33 click.echo(f'The most likely encryption key is {best_key}. It gives the following plaintext:\n\n{best_plaintext[:1000]}...')
34 output_file.write(best_plaintext)
35
36if __name__ == '__main__':
37 caesar_breaker()
38view raw
不仔细看的话可能都看不出有什么区别,因为区别只有四个字母 tqdm ,阿拉伯语中 tqdm 是进度的意思。
tqdm 库的用法非常简单,只要把代码中的迭代器用 tqdm 括起来就行了:
1for key in tqdm(range(26)):
这样就会在命令行输出一个进度条,简单得让人不敢相信。
其实 click 的 click.progress_bar 也有类似的功能,但我觉得 click 的进度条不好看而且写法比tqdm 稍微麻烦一点。
总结一下希望大家读完这篇文章能把设计 Python 命令行的这几个原则用到实践中去写出更好用的 Python 命令行。
原文链接:
https://blog.sicara.com/perfect-python-command-line-interfaces-7d5d4efad6a2
(*本文由Python大本营整理,转载请联系微信1092722531)
◆
推荐
◆
推荐阅读:
如何写出符合Python审美的代码风格?
年度重磅:《AI聚变:2018年优秀AI应用案例TOP 20》正式发布
MIT新福利,2019人工智能公开课上线啦!
漫画:如何实现大整数相乘?(下)
力压今日头条成 App Store 榜第一,个税 App 惊爆 62 例木马病毒!
狼性文化遭质疑,那我们当个佛系程序员可好?
19位专家年末论深冬:区块链究竟还能否回暖?
老程序员肺腑忠告:千万别一辈子靠技术生存!
微软彻底拥抱Python!
相关文章:
送给那些还在迷茫的人
在现在这个高节奏的高效率的时代,两级分化特别厉害。有钱的会越来越有钱,没钱的要么安于现状,要么就越来越穷。 这种思维的产生根源就是不懂得把握机会,其实机会这个东西很奇妙,有时候我们自己也能发现有个机会就摆…

cisco路由器与QOS技术
1 前言随着因特网的普及,网络和人们生活的关系愈加密切,多样化的业务(数据、语音、视频等)应运而生。通常,这些业务对于通信条件的要求各不相同:数据业务对传递的可靠性要求非常高,误码率是最重…

shell介绍,命令历史,命令补全和别名 ,通配符, 输入输出重定向
2019独角兽企业重金招聘Python工程师标准>>> shell介绍 shell是一个命令解释器,提供用户和机器之间的交互; 支持特定语法,比如逻辑判断、循环; 每个用户都可以有自己特定的shell; CentOS7默认shell为bash (…

2018年Python开源项目Top100!只在这里!
整理 | Jane出品 | Python大本营2018 年的最后一天,营长为大家新鲜出炉了一份 2018 年 Python 开源项目 Top100 清单!这些项目都是营长每月通过收集 Mybridge 的数据整理而来的,是不是很棒!(我要在留言区看到你们夸我~…

亲君圆梦,创业一起来
你想像中的创业,是不是办公地点找好了吗?需要注册公司一步一步来呢做哪个行业好呢有没有什么赚钱的项目呢没有你想的那么困难只需要一部手机发个链接点一下 玩一年 创业不花一分钱 校园创业 只需动动手指想要带你一起赚钱一起飞吗?我在厦门亲…

半途而废的香山之旅
今天终于下定决心去香山了!大清早,就和同学小燕两个人买好了路上吃的东西。顶着北京早晨凉爽但有点阻的风骑车自行车向香山的方向进发了!这是我第一次去香山,路线也只能是摸索了!我本来是准备从北四环西路一直往西骑,然…

POwershell 更改文件权限
今天需要给某个网络共享的大文件重新配置一个权限。这个文件夹下面有很多乱七八糟的小文件,很多创建人甚至已经离开公司了。如果一个个地目录手动修改所有者权限,再打开继承关系,这样比较麻烦,这个时候自然是用脚本比较方便了。 1…

“清华帮”AI造富
作者 | 马程;编辑 | 罗丽娟来源 | 全天候科技【导语】如今的AI创业者中,有一大批人出自精英汇聚的 “姚班”和清华的各个院系。他们们凭借技术傍身迅速打造出一批AI独角兽,但在发展过程中,他们又不得不面对资本的压力,…
什么时候是创业最佳时机?7个最佳的励志创业时机GET了吗
1、年轻时人们常说“出名要趁早”,创业也是同样的道理,创业越早,成功的几率就越大。年轻是一种巨大的财富,是无知和天真的完美结合,吸取经验可以作为愚蠢决定的借口。有人说,和职业篮球运动员一样ÿ…

连接ORACLE实例
public class OracleConn {public static Connection getConn() {Connection conn null;try {Class.forName("oracle:jdbc.driver.OracleDriver");String url "jdbc:oracle:thin:localhost:1521:数据库SID";String user "数据库用户名";String…

用Inno Setup来解决.NetFramework安装问题
前段时间朋友接了一个项目,具体是开发一个安装在局域网内的软件,这个软件会定时连接局域网内的服务器来更新本地客户端的一些信息,因为在局域网内存在着多种不同的Windows版本,从WindowsXP、Windows2003及Windows2008到Windows7等…
我想,有间花房
你带我走进你的花房,我无法逃脱花的清香,我不知不觉忘记了方向,你说我世上最坚强,我说你世上最善良,你不知不觉和花儿一样也许每一位 爱花的姑娘,都想有一间属于自己 的花 房 ,在悠闲的午后&…

JS重写提示框(confirm)
<script language"javascript"> /** * 功能:显示提示窗口 * 作者:申楠 qq:38371354 email:amushen1yahoo.com.cn http;//amushen.cnblogs.com * 日期:2005-10-26 * 版本:1.1 * 备注&#…

实现通用人工智能还要多久?Hinton与AlphaGo之父这样回答
作者 | Klye Wiggers 译者 | 刘旭坤 责编 | 琥珀 出品 | AI科技大本营(公众号ID:rgznai100) 人工智能(AI)在即将过去的 2018 年进展神速,取得了很多令人瞩目的成就,比如预测用户感兴趣的音乐…

SQL Server 2008备份大全实战(七)
这一篇博文探讨下SQL Server 2008 备份的实际操作,事实上SQL Server 2008并不关心数据是备份到物理磁盘上还是磁带上,在SQL Server 2008数据库备份中,预定义的目标位置叫做设备。这里设备是对硬盘,磁带机等备份存储的通称。通俗些…

一个中心、三大原则,阿里这样做智能对话开发平台
作者 | 阿里巴巴高级算法专家 李永彬(水德)整理 | 一一出品 | AI科技大本营在阿里巴巴的X峰会上,阿里巴巴-智能服务事业部高级算法专家李永彬(水德)分享了小蜜智能开发平台的构建,他围绕平台来源、设计理念…
大学生目前普遍存在的问题,看你中招了没?
大学几多歌 上课时清醒没有发呆的多,发呆没有睡觉的多,睡觉没有玩手机的多。下课时自习没有吃零食多,吃零食没有看连续剧多,看连续剧没有游戏多。吃饭时吃早餐的没有吃午餐/晚餐的多,吃午餐/晚餐的没有点外卖的多。听讲…

javascript用感
最近两天一直与javascript打交道,现在想总结下所有值处 以下类型都是自己概括别名 1.创建型 例子: var objdocument.createElement("div");//例如创建个DIV var div1document.createElement("div"); div1.id"ddv"; div1.style.w…

Vulkan Tutorial 12 Fixed functions
Vertex input VkPipelineVertexInputStateCreateInfo结构体描述了顶点数据的格式,该结构体数据传递到vertex shader中。它以两种方式进行描述: Bindings:根据数据的间隙,确定数据是每个顶点或者是每个instance(instancing) Attribute 描述:描述将要进行…

年后跳槽BAT必看:10种数据结构、算法和编程课助你面试通关
作者 | javinpaul译者 | 大鱼编辑 | 一一出品 | AI 科技大本营进入 BAT 这样的巨头企业工作,无疑是很多程序员的梦想。但事实上,能通过这些公司高难度编程面试的只是一小撮人,大多数人因为理论知识和项目实践的匮乏导致在面试后很快被淘汰&am…

戚薇在冰箱放香水,是贫穷限制了想象力!
前有李诞冰箱里放面膜,今有戚薇冰箱里放香水,《拜托了冰箱》真是为观众们打开了新世界的大门。在昨晚播出的节目中,戚薇充分展现了她作为“美妆博主”的实力,冰箱里摆满的香水不仅让冰箱家族看得目瞪口呆,网友们也是激…

使用apache的activemq集合JMS处理异步消息
为什么80%的码农都做不了架构师?>>> 1:先去apache下载 http://activemq.apache.org/ ,大约有25M,要有点耐心 直接打开就可以使用;当然它也提供了安装为windows service的方法 2:配置activemq 在…
中国大学生创业报告发布
由中国人民大学牵头,北京师范大学、上海交通大学等30余家高校、企业和社会组织联合跟踪调查的《2017年中国大学生创业报告近日发布。这份覆盖全国52所高校的报告表明,大学生创业意愿持续高涨,大学生创业层次也在不断提升,但大学生…

windows server 2012 application control policy
启用Application Control policy 组策略设置,可以实现基于用户或组的应用程序权限控制,有两点需要注意: 1、策略的顺序是从上到下执行的,allow的策略需要放在上面。 2、在客户端上,或者在远程桌面的服务器上需要启用ap…

你已经是个成熟的表格,该学会NLP了
作者 | 唐都钰、孙一博来源 | 微软亚洲研究院AI头条编者按:在我们的生活中,用语音查询天气,用必应搜索信息,这些常见的场景都离不开一种应用广泛的数据存储方式——表格(table)。如果让表格更智能一些&…

[Design] Flyweight Pattern
结构模式 结构模式描述如何将类或者类的对象结合在一起形成更大的结构。 结构模式描述两种不同的东西:类与类的实例。结构模式可以分为:类的结构模式和对象的结构模式两种。 类的结构模式:类的结构模式使用继承来把类、接口等组合在一…

网友们票选的2018 Best Paper,你pick谁?
整理 | 琥珀出品 | AI科技大本营不久前,Reddit 机器学习论坛上一位网友发布了一个帖子:“What is the best ML paper you read in 2018 and why?(你认为 2018 年读过的最好的论文是哪篇?)”吸引了各路网友前来回答。营…
微信小游戏创业,究竟是红海还是死海?
“跳一跳”、“弹球王者”、“海盗来了”、“斗地主”等等微信小游戏,你是不是很眼熟呢?这些都是前段时间被刷了屏的小游戏,以“海盗来了”为例,据透露,其月流水已经突破了 1000 万,单日流水峰值也达到了 2…

OSPF中的frame-relay(3) 点到多点非广播
hostname R1!interface Loopback0 ip address 1.1.1.1 255.255.255.255interface Serial0 ip address 192.168.1.1 255.255.255.0 encapsulation frame-relay ip ospf network point-to-multipoint non-broadcast //将接口类型指定为点对多点非广播 frame-relay map ip 192.1…

一步一步SharePoint 2007之十六:注册并配置一个网站用户
在前面的文章中,我已经介绍了如何创建管理帐户。创建其它帐户的方法是一样的。大家可以把这个注册系统放到自己的网站中,就可以成为网站的一部分了。本文将只讲解如何在管理工具中将用户手动加入到网站用户组中,至于如何将注册完后的用户自动…