网易首页 > 网易号 > 正文 申请入驻

技术流 手把手教你用Python设计一个命令行界面

0
分享至

  作者 | Yannick Wolff

  译者 | 刘旭坤

  整理 | Jane

  出品 | Python大本营

  对 Python 程序来说,完备的命令行界面可以提升团队的工作效率,减少调用时可能碰到的困扰。今天,我们就来教大家如何设计功能完整的 Python 命令行界面。

  对 Python 开发者来说用的最多的界面恐怕还是命令行。就拿我参与的机器学习项目来说,训练模型和评估算法的精确度都是通过在命令行界面运行脚本来完成的。

  所以调用一个 Python 脚本的时候我们希望这段脚本有一个尽量简洁方便调用的接口。尤其是团队中有多名开发者的时候这一点对提升团队的工作效率很重要。

  要让一段脚本方便调用总的来说有四个原则需要遵守:

  提供默认参数值

  处理调用出错的情况,比如缺少参数、参数类型错误或者找不到文件等

  在文档中说明各个参数和选项的用法

  如果执行时间较长应该提供进度条

  一个简单的例子

  下面我们先通过一个简单的例子来谈谈这四个原则的具体应用。例子中给出的脚本的功能是使用凯撒码变换对文本进行加密和解密。

  Caesar cipher:一种简单的消息编码方式。在密码学中,凯撒密码,移位密码是最简单和最广为人知的加密技术之一。

  比如说我们想让用户通过命令行参数来选择调用的方式是加密还是解密文本,而且用户要从命令行传入下面encrypt函数中的密匙参数 key。

  1defencrypt(plaintext,key):
2cyphertext=''
3forcharacterinplaintext:
4ifcharacter.isalpha():
5number=ord(character)
6number+=key
7ifcharacter.isupper():
8ifnumber>ord('Z'):
9number-=26
10elifnumber
11number+=26
12elifcharacter.islower():
13ifnumber>ord('z'):
14number-=26
15elifnumber
16number+=26
17character=chr(number)
18cyphertext+=character
19
20returncyphertext

  首先我们得在程序中拿到命令行参数。我在网上搜“ python 命令行参数”出来的第一个结果说让我用sys.argv,那我们就来试试看它好不好用。

  初级:笨办法

  其实sys.argv只是一个 list ,这个 list 的内容是用户调用脚本时所输入的所有参数(其中也包括脚本的文件名)。

  如果我像下面这样调用加解密的脚本 caesar_script.py 的话:

  1>pythoncaesar_script.py--key23--decryptmysecretmessage
2pbvhfuhwphvvdjh

  sys.argv 这个 list 的值就是:

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

  所以我们现在要遍历这个 list 来找其中是否包括了“–key”或者“-k”,这样我们就能找到密匙“ 23 ”。再找到“–decrypt”就能知道用户是想要解密一段文本了(其实解密就是用密匙的相反数再加密一次)。

  完成后的代码如下:

  1importsys
2
3fromcaesar_encryptionimportencrypt
4
5
6defcaesar():
7key=1
8is_error=False
9
10forindex,arginenumerate(sys.argv):
11ifargin['--key','-k']andlen(sys.argv)>index+1:
12key=int(sys.argv[index+1])
13delsys.argv[index]
14delsys.argv[index]
15break
16
17forindex,arginenumerate(sys.argv):
18ifargin['--encrypt','-e']:
19delsys.argv[index]
20break
21ifargin['--decrypt','-d']:
22key=-key
23delsys.argv[index]
24break
25
26iflen(sys.argv)==1:
27is_error=True
28else:
29forarginsys.argv:
30ifarg.startswith('-'):
31is_error=True
32
33ifis_error:
34print(f'Usage:python{sys.argv[0]}[--key][--encrypt|decrypt]')
35else:
36print(encrypt(''.join(sys.argv[1:]),key))
37
38if__name__=='__main__':
39caesar()

  这段代码基本上遵守了我们提到的四个原则:

  key 和 加密模式都设置了缺省参数

  脚本可以处理像没有文本或者缺少参数这样比较基本的错误

  用户没有给参数或者有错的话会显示使用帮助

  1>pythoncaesar_script_using_sys_argv.py
2Usage:pythoncaesar.py[--key][--encrypt|decrypt]

  然而不算加密函数光处理参数我们就已经写了 39 行而且写得一点也不优雅。我有胆说肯定还有更好的办法来读命令行参数。

  中级:argparse

  Python 标准库里面提供了一个读取命令行参数的库——argparse 。我们来看看如果用 argparse 代码怎么写:

  1importargparse
2
3fromcaesar_encryptionimportencrypt
4
5
6defcaesar():
7parser=argparse.ArgumentParser()
8group=parser.add_mutually_exclusive_group()
9group.add_argument('-e','--encrypt',action='store_true')
10group.add_argument('-d','--decrypt',action='store_true')
11parser.add_argument('text',nargs='*')
12parser.add_argument('-k','--key',type=int,default=1)
13args=parser.parse_args()
14
15text_string=''.join(args.text)
16key=args.key
17ifargs.decrypt:
18key=-key
19cyphertext=encrypt(text_string,key)
20print(cyphertext)
21
22if__name__=='__main__':
23caesar()
24viewraw

  这样写也符合四项指导原则,而且对参数的说明和错误处理都优于使用 sys.argv 的笨办法:

  1>pythoncaesar_script_using_argparse.py--encodeMymessage
2
3usage:caesar_script_using_argparse.py[-h][-e|-d][-kKEY][text[text...]]
4caesar_script_using_argparse.py:error:unrecognizedarguments:--encode
5>pythoncaesar_script_using_argparse.py--help
6
7usage:caesar_script_using_argparse.py[-h][-e|-d][-kKEY][text[text...]]
8positionalarguments:
9text
10optionalarguments:
11-h,--helpshowthishelpmessageandexit
12-e,--encrypt
13-d,--decrypt
14-kKEY,--keyKEY

  不过我个人还是觉得代码里第 7 行到第 13 行定义参数的部分写得很啰嗦,而且我觉得参数应该使用声明式的方法来定义。

  高级: click

  还有一个叫 click 的库能实现我们想要的这些。它的基本功能和 argparse 是一样的,但写出来的代码更优雅。

  使用 click 改写我们的加解密脚本之后是这样的:

  1importclick
2
3fromcaesar_encryptionimportencrypt
4
5@click.command()
6@click.argument('text',nargs=-1)
7@click.option('--decrypt/--encrypt','-d/-e')
8@click.option('--key','-k',default=1)
9defcaesar(text,decrypt,key):
10text_string=''.join(text)
11ifdecrypt:
12key=-key
13cyphertext=encrypt(text_string,key)
14click.echo(cyphertext)
15
16if__name__=='__main__':
17caesar()
18viewraw

  我们需要的参数和选项都用装饰器来声明,这样就可以在 caesar 函数里直接使用了。

  上面的代码里有几点需要说明:

  nargs 参数是说这个参数的长度是几个词。默认值是 1 不过用引号引起来的句子也只算一个词。这里我们设为 -1 是指不限制长度。

  --decrypt/--encrypt 这样加一个斜杠的写法用来指明互斥的选项,它的功能和 argparse 中的 add_mutually_exclusive_group 函数类似。

  click.echo 是 click 提供的一个 print 功能,与 Python 2 和 3 都兼容,而且有颜色高亮功能。

  添加隐私功能

  我们写的是一个对文本加解密的脚本,但用户却直接把要加密的文本打出来了,这样有别人用这个命令行的话按几下上方向键就能看到我们的用户加密了什么东西,这是在是有点荒唐。

  我们可以选择把用户要加密的文本隐藏起来,或者是从文件里读文本。这两种方法都能解决我们的问题,但选择权应该留给用户。

  同理对于加解密的结果我们也让用户选择是直接在命令行输出还是保存成一个文件:

  1importclick
2
3fromcaesar_encryptionimportencrypt
4
5@click.command()
6@click.option(
7'--input_file',
8type=click.File('r'),
9help='Fileinwhichthereisthetextyouwanttoencrypt/decrypt.'
10'Ifnotprovided,apromptwillallowyoutotypetheinputtext.',
11)
12@click.option(
13'--output_file',
14type=click.File('w'),
15help='Fileinwhichtheencrypted/decryptedtextwillbewritten.'
16'Ifnotprovided,theoutputtextwilljustbeprinted.',
17)
18@click.option(
19'--decrypt/--encrypt',
20'-d/-e',
21help='Whetheryouwanttoencrypttheinputtextordecryptit.'
22)
23@click.option(
24'--key',
25'-k',
26default=1,
27help='Thenumerickeytouseforthecaesarencryption/decryption.'
28)
29defcaesar(input_file,output_file,decrypt,key):
30ifinput_file:
31text=input_file.read()
32else:
33text=click.prompt('Enteratext',hide_input=notdecrypt)
34ifdecrypt:
35key=-key
36cyphertext=encrypt(text,key)
37ifoutput_file:
38output_file.write(cyphertext)
39else:
40click.echo(cyphertext)
41
42if__name__=='__main__':
43caesar()
44viewraw

  这里我给每个参数和选项都加上了一小段说明,这样我们的文档能更清楚一点因为我们现在参数有点多了。现在的文档是这样的:

  1>pythoncaesar_script_v2.py--help
2Usage:caesar_script_v2.py[OPTIONS]
3Options:
4--input_fileFILENAMEFileinwhichthereisthetextyouwanttoencrypt/decrypt.Ifnotprovided,apromptwillallowyoutotypetheinputtext.
5--output_fileFILENAMEFileinwhichtheencrypted/decryptedtextwillbewritten.Ifnotprovided,theoutputtextwilljustbeprinted.
6-d,--decrypt/-e,--encryptWhetheryouwanttoencrypttheinputtextordecryptit.
7-k,--keyINTEGERThenumerickeytouseforthecaesarencryption/decryption.
8--helpShowthismessageandexit.

  两个新的参数 input_file 和 output_file 都是 click.File 类型,而且 click 帮我们处理了文件打开的读写方式和可能出现的错误,比如这样:

  1>pythoncaesar_script_v2.py--decrypt--input_filewrong_file.txt
2Usage:caesar_script_v2.py[OPTIONS]
3Error:Invalidvaluefor"--input_file":Couldnotopenfile:wrong_file.txt:Nosuchfileordirectory

  如果用户没有提供 input_file 的话,如说明文档中所写,则会让用户在命令行进行输入,而且用户输入不再是明文了:

  1>pythoncaesar_script_v2.py--encrypt--key2
2Enteratext:**************
3yyy.ukectc.eqo

  破译密码

  假设我们现在是黑客,想解密但是不知道密匙该怎么办呢?对凯撒加密的英文来说很容易,只要调用解密函数 25 次然后看看那个结果不是乱码就行了。

  要调用 25 次还要一个一个看还是太麻烦,其实只要数数哪个结果里正确的英文词最多就行了。下面我们就用 PyEnchant 来实现自动破译密码:

  1importclick
2importenchant
3
4fromcaesar_encryptionimportencrypt
5
6@click.command()
7@click.option(
8'--input_file',
9type=click.File('r'),
10required=True,
11)
12@click.option(
13'--output_file',
14type=click.File('w'),
15required=True,
16)
17defcaesar_breaker(input_file,output_file):
18cyphertext=input_file.read()
19english_dictionnary=enchant.Dict("en_US")
20max_number_of_english_words=0
21forkeyinrange(26):
22plaintext=encrypt(cyphertext,-key)
23number_of_english_words=0
24forwordinplaintext.split(''):
25ifwordandenglish_dictionnary.check(word):
26number_of_english_words+=1
27ifnumber_of_english_words>max_number_of_english_words:
28max_number_of_english_words=number_of_english_words
29best_plaintext=plaintext
30best_key=key
31click.echo(f'Themostlikelyencryptionkeyis{best_key}.Itgivesthefollowingplaintext:\n\n{best_plaintext[:1000]}...')
32output_file.write(best_plaintext)
33
34if__name__=='__main__':
35caesar_breaker()
36viewraw

  一气呵成!

  不过我们好像还没有提到四项原则的最后一点:

  4.如果执行时间较长应该提供进度条

  上面的脚本破译 104 个词的文本大约需要 5 秒。考虑到要遍历 25 个密匙还要数英文词的个数这个时间并不算慢。

  不过文本再长的话,比如 105 个词的文本,就要花 50 秒。这就有点长了,用户可能没有耐心等到程序运行完就强退了。

  所以我建议如果执行时间长的话最好加上进度条,关键是写起来非常简单:

  1importclick
2importenchant
3
4fromtqdmimporttqdm
5
6fromcaesar_encryptionimportencrypt
7
8@click.command()
9@click.option(
10'--input_file',
11type=click.File('r'),
12required=True,
13)
14@click.option(
15'--output_file',
16type=click.File('w'),
17required=True,
18)
19defcaesar_breaker(input_file,output_file):
20cyphertext=input_file.read()
21english_dictionnary=enchant.Dict("en_US")
22best_number_of_english_words=0
23forkeyintqdm(range(26)):
24plaintext=encrypt(cyphertext,-key)
25number_of_english_words=0
26forwordinplaintext.split(''):
27ifwordandenglish_dictionnary.check(word):
28number_of_english_words+=1
29ifnumber_of_english_words>best_number_of_english_words:
30best_number_of_english_words=number_of_english_words
31best_plaintext=plaintext
32best_key=key
33click.echo(f'Themostlikelyencryptionkeyis{best_key}.Itgivesthefollowingplaintext:\n\n{best_plaintext[:1000]}...')
34output_file.write(best_plaintext)
35
36if__name__=='__main__':
37caesar_breaker()
38viewraw

  不仔细看的话可能都看不出有什么区别,因为区别只有四个字母 tqdm ,阿拉伯语中 tqdm 是进度的意思。

  tqdm 库的用法非常简单,只要把代码中的迭代器用 tqdm 括起来就行了:

  1forkeyintqdm(range(26)):

  这样就会在命令行输出一个进度条,简单得让人不敢相信。

  其实 click 的 click.progress_bar 也有类似的功能,但我觉得 click 的进度条不好看而且写法比tqdm 稍微麻烦一点。

  总结一下希望大家读完这篇文章能把设计 Python 命令行的这几个原则用到实践中去写出更好用的 Python 命令行。

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

  (*本文由Python大本营整理,转载请联系微信1092722531)

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
必须严惩!李楠刚复出,北京队又出现打假球,中国篮协忍无可忍

必须严惩!李楠刚复出,北京队又出现打假球,中国篮协忍无可忍

宗介说体育
2026-06-26 09:31:40
台积电:防了大陆几十年,结果副总是美国间谍,核心机密全被卷走

台积电:防了大陆几十年,结果副总是美国间谍,核心机密全被卷走

阿器谈史
2026-06-25 14:29:50
莫斯科拉响警报!泽连斯基触碰逆鳞,俄军战法大变,连炸两座大桥

莫斯科拉响警报!泽连斯基触碰逆鳞,俄军战法大变,连炸两座大桥

肖兹探秘说
2026-06-25 14:22:26
深圳最牛街道突然“凉了”?房东慌了

深圳最牛街道突然“凉了”?房东慌了

地产一品塘
2026-06-26 08:00:32
韩媒不满日本平局:4球大胜时的他们去哪了?本来还指望他们帮忙

韩媒不满日本平局:4球大胜时的他们去哪了?本来还指望他们帮忙

行舟问茶
2026-06-26 10:26:00
梁朝伟和汤唯在《色戒》里“假戏真做”?网友爆出截图:一目了然

梁朝伟和汤唯在《色戒》里“假戏真做”?网友爆出截图:一目了然

姜糖先生
2025-06-08 19:31:00
尊界超高端车型138.8万开售,余承东:这一价格诚意满满

尊界超高端车型138.8万开售,余承东:这一价格诚意满满

南方都市报
2026-06-26 10:19:10
逆市走红!“老登股”净利预增超23倍,股价一度涨停

逆市走红!“老登股”净利预增超23倍,股价一度涨停

21世纪经济报道
2026-06-26 11:16:05
2026年浙江高考前10名新鲜出炉,分别来自这些学校

2026年浙江高考前10名新鲜出炉,分别来自这些学校

乡土宁海
2026-06-25 22:01:40
精神小妹的生活原来是这样的!网友:终于知道她们为啥都这么瘦了

精神小妹的生活原来是这样的!网友:终于知道她们为啥都这么瘦了

深度报
2026-02-11 23:35:03
怂了!不要特雷·杨的3号了!

怂了!不要特雷·杨的3号了!

篮球大图
2026-06-26 11:50:55
叶挺:当新四军长起初并不被延安认可,不早逝也不太可能成为元帅

叶挺:当新四军长起初并不被延安认可,不早逝也不太可能成为元帅

阿胡
2025-04-29 16:28:14
李飞飞最新访谈:10年后,只会剩下2类工作者

李飞飞最新访谈:10年后,只会剩下2类工作者

笔记侠
2026-06-24 02:08:14
真是怕啥来啥!日本不帮,德国补刀:韩国队离世界杯出局更近了

真是怕啥来啥!日本不帮,德国补刀:韩国队离世界杯出局更近了

足球大腕
2026-06-26 09:47:44
“我们允许你儿子考0分!”家长晒报恩男孩,成绩已经没有意义了

“我们允许你儿子考0分!”家长晒报恩男孩,成绩已经没有意义了

世界圈
2026-06-26 08:33:20
A股:今天早盘加速跳水破4075,种种迹象表明,A股牛市已经开始熄火?

A股:今天早盘加速跳水破4075,种种迹象表明,A股牛市已经开始熄火?

股侠指北针
2026-06-26 10:05:14
沈梦辰和杜海涛路透照公开,“沈梦辰全身涂粉底液”登上热搜

沈梦辰和杜海涛路透照公开,“沈梦辰全身涂粉底液”登上热搜

韩小娱
2026-06-22 15:46:33
最新:曝乌克兰攻入俄罗斯领土!摧毁布良斯克铁路系统

最新:曝乌克兰攻入俄罗斯领土!摧毁布良斯克铁路系统

项鹏飞
2026-06-23 20:33:45
近70万海归挤回国,海归硕士求职被一句“我们不承认”给堵了回去

近70万海归挤回国,海归硕士求职被一句“我们不承认”给堵了回去

小叨娱乐
2026-06-04 11:26:37
你无意中发现了不得的事?网友:大保健里遇见嫂子

你无意中发现了不得的事?网友:大保健里遇见嫂子

夜深爱杂谈
2026-05-28 07:59:33
2026-06-26 13:40:49
AI科技大本营 incentive-icons
AI科技大本营
连接AI技术的创造者和使用者
2731文章数 7707关注度
往期回顾 全部

科技要闻

美国政府要求OpenAI分批发布GPT-5.6

头条要闻

13岁男孩失踪5天救援人员失去信心 妈妈坚持下找到了

头条要闻

13岁男孩失踪5天救援人员失去信心 妈妈坚持下找到了

体育要闻

奥尔莫:是时候为西班牙争夺第二颗星了

娱乐要闻

刘嘉玲想放弃梁朝伟,没有自理能力

财经要闻

悬在科技头上的达摩克利斯之剑

汽车要闻

老板们的新座驾!65万元起,尊界V800/V680开启预订

态度原创

时尚
本地
艺术
房产
公开课

《铁拳教育》为老师写的红果短剧

本地新闻

2026世界杯全勤太难?这份保姆级攻略请收好

艺术要闻

“史上最热夏天”?

房产要闻

城市精英集体出手!科学城这一现象级热销红盘,凭何成为共识之选?

公开课

李玫瑾:为什么性格比能力更重要?

无障碍浏览 进入关怀版