这篇文章将教你如何编写完美的 Python 命令行程序,提高团队的生产力,让大家的工作更舒适。

作者 | Yannick Wolff

译者 | 弯月

责编 | 屠敏

出品 | CSDN(ID:CSDNNews)

作为 Python 开发者,我们经常要编写命令行程序。比如在我的数据科学项目中,我要从命令行运行脚本来训练模型,以及计算算法的准确率等。

因此,更方便更易用的脚本能够很好地提高生产力,特别是在有多个开发者从事同一个项目的场合下。

因此,我建议你遵循以下四条规则:

  1. 尽可能提供默认参数值

  2. 所有错误情况必须处理(例如,参数缺失,类型错误,找不到文件)

  3. 所有参数和选项必须有文档

  4. 不是立即完成的任务应当显示进度条

举个简单的例子

我们把这些规则应用到一个具体的例子上。这个脚本可以使用凯撒加密法加密和解密消息。

假设已经有个写好的 encrypt 函数(实现如下),我们需要创建一个简单的脚本,用来加密和解密消息。我们希望让用户通过命令行参数选择加密模式(默认)和解密模式,并选择一个秘钥(默认为 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

我们的脚本需要做的第一件事就是获取命令行参数的值。当我搜索“python command line arguments”时,出现的第一个结果是关于sys.argv的,所以我们来试试这个方法……

“初学者”的方法

sys.argv 是个列表,包含用户在运行脚本时输入的所有参数(包括脚本名自身)。

例如,如果我输入:

> python caesar_script.py --key 23 --decrypt my secret messagepb vhfuhw phvvdjh

该列表将包含:

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

因此只需遍历该参数列表,找到'--key'(或'-k')以得到秘钥值,找到'--decrypt'以设置解密模式(实际上只需要使用秘钥的反转作为秘钥即可)。

最后我们的脚本大致如下:

import sys

from caesar_encryption import encrypt

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 <key> ] [ --encrypt|decrypt ] <text>')    else:        print(encrypt(' '.join(sys.argv[1:]), key))

if __name__ == '__main__':    caesar()

这个脚本遵循了一些我们前面推荐的规则:

  1. 支持默认秘钥和默认模式

  2. 基本的错误处理(没有提供输入文本的情况,以及提供了无法识别的参数的情况)

  3. 出错时或者不带任何参数调用脚本时会显示文档:

> python caesar_script_using_sys_argv.pyUsage: python caesar.py [ --key <key> ] [ --encrypt|decrypt ] <text>

但是,这个凯撒加密法脚本太长了(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:  textoptional arguments:  -h, --help         show this help message and exit  -e, --encrypt  -d, --decrypt  -k KEY, --key KEY

但是,仔细看了这段代码后,我发现(虽然有点主观)函数开头的几行(从7行到13行)定义了参数,但定义方式并不太优雅:它太臃肿了,而且完全是程式化的。应该有更描述性、更简洁的方法。

click 能做得更好!

幸运的是,有个 Python 库能提供与 argparse 同样的功能(甚至还能提供更多),它的代码风格更优雅。这个库的名字叫 click。

这里是凯撒加密脚本的第三版,使用了 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这种写法可以定义完全互斥的选项(类似于argparse中的add_mutually_exclusive_group函数),它将产生一个布尔型参数。

  • click.echo是该库提供的一个工具函数,它的功能与print相同,但兼容Python 2和Python 3,还有一些其他功能(如处理颜色等)。

添加一些隐秘性

这个脚本的参数(被加密的消息)应当是最高机密。而我们却要求用户直接在终端里输入文本,使得这些文本被记录在命令历史中,这不是很讽刺吗?

解决方法之一就是使用隐藏的提示。或者可以从输入文件中读取文本,对于较长的文本来说更实际一些。或者可以干脆让用户选择。

输出也一样:用户可以保存到文件中,也可以输出到终端。这样就得到了凯撒脚本的最后一个版本:

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()

这个版本有什么新东西吗?

  • 首先,注意到我给每个参数选项都加了个help参数。由于脚本变得复杂了,help参数可以给脚本的行为添加一些文档。运行结果如下:

> python caesar_script_v2.py --helpUsage: 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.
  • 两个新的参数:input_file 和 output_file,类型均为 click.File。该库能够用正确的模式打开文件,处理可能的错误,再执行函数。例如:

> python caesar_script_v2.py --decrypt --input_file wrong_file.txtUsage: caesar_script_v2.py [OPTIONS]Error: Invalid value for "--input_file": Could not open file: wrong_file.txt: No such file or directory
  • 正像help文本中解释的那样,如果没有提供input_file,就使用click.promp让用户直接在提示符下输入文本,在加密模式下这些文本是隐藏的。如下所示:

> python caesar_script_v2.py --encrypt --key 2Enter a text: **************yyy.ukectc.eqo

破解密文!

现在设想你是个黑客:你要解密一个用凯撒加密过的密文,但你不知道秘钥是什么。

最简单的策略就是用所有可能的秘钥调用解密函数 25 次,阅读解密结果,看看哪个是合理的。

但你很聪明,而且也很懒,所以你想让整个过程自动化。确定解密后的 25 个文本哪个最可能是原始文本的方法之一,就是统计所有这些文本中的英文单词的个数。这可以使用 PyEnchant 模块实现:

import clickimport 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()

貌似运行得很不错,但别忘了,好的命令行程序还有个规则需要遵守:

4.A 不是立即完成的任务应当显示进度条。

示例中的文本包含10^4个单词,因此该脚本需要大约5秒才能解密。这很正常,因为它需要检查所有25个秘钥,每个秘钥都要检查10^4个单词是否出现在英文字典中。

假设你要解密的文本包括10^5个但IC,那么就要花费50秒才能输出结果,用户可能会非常着急。

因此我建议这种任务一定要显示进度条。特别是,显示进度条还非常容易实现。

下面是个显示进度条的例子:

import clickimport 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。

tqdm 是 Python 库的名字,也是它包含的类的名字。只需用它包裹一个可迭代的东西,就能显示出进度条:

for key in tqdm(range(26)):

这样就能显示出非常漂亮的进度条。我都不敢相信这是真的。

另外,click也提供类似的显示进度条的工具(click.progress_bar),但我觉得它的外观不太容易懂,而且要写的代码也多一些。

我希望这篇文章能让你在改进开发者的体验上多花点时间。

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

作者:Yannick Wolff,Sicara 的数据科学家。

本文为 CSDN 翻译,如需转载,请注明来源出处。

 热 文 推 荐 

☞ MongoDB 凉了?

☞ 和 C++ 相比,我为什么要选择 Rust 来开发软件?

☞ 如何设计一个实用的线程池?

☞“对不起,你的离职是我的错!”

☞ Gartner的预言:通向混合IT之旅

☞ 阿里“菜鸟”AI?

☞ 刚刚!华为又被美国盯上了!

☞ 心疼!能为程序员男友做些什么吗?

print_r('点个好看吧!');
var_dump('点个好看吧!');
NSLog(@"点个好看吧!");
System.out.println("点个好看吧!");
console.log("点个好看吧!");
print("点个好看吧!");
printf("点个好看吧!");
cout << "点个好看吧!" << endl;
Console.WriteLine("点个好看吧!");
fmt.Println("点个好看吧!");
Response.Write("点个好看吧!");
alert("点个好看吧!")
echo "点个好看吧!"

点击“阅读原文”,打开 CSDN App 阅读更贴心!

喜欢就点击“好看”吧

如何编写完美的 Python 命令行程序?相关推荐

  1. 每日一课 | 如何编写完美的Python命令行程序?

    作者 | Yannick Wolff 译者 | 弯月 作为 Python 开发者,我们经常要编写命令行程序.比如在我的数据科学项目中,我要从命令行运行脚本来训练模型,以及计算算法的准确率等. 因此,更 ...

  2. 使用click创建完美的Python命令行程序

    Python程序员的主要工作是写命令行程序,即直接在终端运行的脚本. 随着项目规模增长,我们希望创建有效的命令行接口,通过提供不同的参数,解决不同的问题,而不是每次都修改源代码. Click库是一个非 ...

  3. python命令行大全-用什么库写 Python 命令行程序(示例代码详解)

    一.前言 在近半年的 Python 命令行旅程中,我们依次学习了 argparse . docopt . click 和 fire 库的特点和用法,逐步了解到 Python 命令行库的设计哲学与演变. ...

  4. python 代码命令大全-用什么库写 Python 命令行程序(示例代码详解)

    一.前言 在近半年的 Python 命令行旅程中,我们依次学习了 argparse . docopt . click 和 fire 库的特点和用法,逐步了解到 Python 命令行库的设计哲学与演变. ...

  5. 你还在纠结用什么库写 Python 命令行程序?看这一篇就够了

    一.前言 在近半年的 Python 命令行旅程中,我们依次学习了 argparse.docopt.click 和 fire 库的特点和用法,逐步了解到 Python 命令行库的设计哲学与演变. 本文作 ...

  6. python用什么敲代码_你还在纠结用什么库写 Python 命令行程序?看这一篇就够了...

    一.前言 在近半年的 Python 命令行旅程中,我们依次学习了 argparse.docopt.click 和 fire 库的特点和用法,逐步了解到 Python 命令行库的设计哲学与演变. 本文作 ...

  7. Shell编写带选项的命令行程序(全短选项)

    如果命令行程序的选项全部为短选项可以通过getopts来解析命令行选项.如下程序test.sh. #! /bin/bash printUsage(){echo "usage: test.sh ...

  8. GDAL源码剖析(五)之Python命令行程序

    一. GDAL Python工具 本文主要介绍的是GDAL工具集中的Python脚本命令,需要的环境必须是有Python环境和GDAL的Python版本.这是必须的,否则这些工具都不能用.对于已经安装 ...

  9. 用JAVA语言编写一个评委评分命令行程序

    该程序需具备如下功能: (1)用户能设定评委人数和选手人数: (2)某选手完成表演后,能输入该选手的姓名,每个评委(评委用数字编号表示,0表示评委1,其他类推)的评分(区间[0,10]上): (3)选 ...

最新文章

  1. rocketmq 消费者不能调用其他服务_Spring Cloud Alibaba RocketMQ - 构建异步通信的微服务...
  2. ABAP bit 操作
  3. jsp九大内置对象与servlet中java对象
  4. 全国计算机一级msoffice考试内容,2015年全国计算机一级MSOffice考试大纲
  5. 汽车平顺性与仿真分析matlab,基于matlab的汽车平顺性的建模与仿真.docx
  6. mysql负变量_MySQL的变量
  7. C#6中的新增功能 【Unity3D亲测】
  8. 流程平台:示例流程 - 主机申请审批流程
  9. Java开发 明华usbkey_明华驱动官方版下载-明华usbkey数字证书驱动下载v3.0.2420.9 最新版-当易网...
  10. winrar中文版去广告的方法,以winrar5.70简体中文版为例(其他版本也类似)
  11. 网络安全现状及防范措施
  12. Arduino学习总结
  13. MarkMan(马克鳗)的下载及安装方法
  14. 【无标题】60秒倒计时
  15. rono在oracle的作用_细节见真章,OPPO Reno多项品质测试,这才是最真实表现
  16. zblog mysql修改_ZBlog教程 之 修改ZBlog数据库前缀
  17. 直流电机的快衰减和慢衰减模式分析
  18. 数位 dp 相邻位数字差值的绝对值不能超过 2_维懂百科——绝对值编码器的“绝对式”的定义...
  19. android studio run app灰色,Apply Changes and Restart Activity灰色
  20. python爬虫个人文档整理

热门文章

  1. python里order什么意思_python中OrderedDict的使用方法详解
  2. yolov5-detect.py解析与重写
  3. python list 的深浅拷贝探索
  4. 编译过程、静态库和动态库
  5. 剑指offer之翻转链表
  6. 前端开发者必备google插件
  7. Python菜鸟入门:day15编程学习
  8. 脉脉因“App 整改下架”事件致歉;阿里云全年营收超 600 亿;腾讯防大量群消息骚扰专利获授权|极客头条...
  9. 滴滴、小米启动造车,特斯拉的护城河还能守多久?
  10. 专栏数量创新高、问答wap端新视觉