怎么编写完美的 Python 命令行程序?

 最新动态     |      2019-02-11 11:29

这篇文章将教你怎么编写完美的 Python 指令行程序,进步团队的生产力,让咱们的作业更舒适。

作者 | Yannick Wolff

译者 | 弯月

责编 | 屠敏

出品 | CSDN(ID:CSDNNews)

作为 Python 开发者,咱们常常要编写指令行程序。比如在我的数据科学项目中,我要从指令行运转脚原本练习模型,以及核算算法的精确率等。

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

因而,我主张你遵从以下四条规矩:

  1. 尽或许供给默许参数值
  2. 一切过错状况有必要处理(例如,参数缺失,类型过错,找不到文件)
  3. 一切参数和选项有必要有文档
  4. 不是当即完结的使命应当显现进度条

举个简略的比如

咱们把这些规矩应用到一个详细的比如上。这个脚本能够运用凯撒加密法加密和解密音讯。

假定已经有个写好的 encrypt 函数(完结如下),咱们需求创立一个简略的脚本,用来加密和解密音讯。咱们期望让用户经过指令行参数挑选加密形式(默许)和解密形式,并挑选一个秘钥(默许为 1)。

defencrypt(plaintext, key):

cyphertext = ''

forcharacter inplaintext:

ifcharacter.isalpha():

number = ord(character)

number += key

ifcharacter.isupper():

ifnumber > ord( 'Z'):

number -= 26

elifnumber < ord( 'A'):

number += 26

elifcharacter.islower():

ifnumber > ord( 'z'):

number -= 26

elifnumber < ord( 'a'):

number += 26

character = chr(number)

cyphertext += character

returncyphertext

咱们的脚本需求做的榜首件事就是获取指令行参数的值。当我查找“python command line arguments”时,呈现的榜首个成果是关于sys.argv的,所以咱们来试试这个办法……

“初学者”的办法

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

例如,假如我输入:

> pythoncaesar_.py--key23 --decryptmysecretmessage

pbvhfuhwphvvdjh

该列表将包括:

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

因而只需遍历该参数列表,找到'--key'(或'-k')以得到秘钥值,找到'--decrypt'以设置解密形式(实践上只需求运用秘钥的回转作为秘钥即可)。

最终咱们的脚本大致如下:

importsys

fromcaesar_encryption importencrypt

defcaesar():

key = 1

is_error = False

forindex, arg inenumerate(sys.argv):

ifarg in[ '--key', '-k'] andlen(sys.argv) > index + 1:

key = int(sys.argv[index + 1])

delsys.argv[index]

delsys.argv[index]

break

forindex, arg inenumerate(sys.argv):

ifarg in[ '--encrypt', '-e']:

delsys.argv[index]

break

ifarg in[ '--decrypt', '-d']:

key = -key

delsys.argv[index]

break

iflen(sys.argv) == 1:

is_error = True

else:

forarg insys.argv:

ifarg.startswith( '-'):

is_error = True

ifis_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. 犯错时或许不带任何参数调用脚本时会显现文档:

> pythoncaesar__using_sys_argv.py

Usage: pythoncaesar.py[ --key <key> ][ --encrypt|decrypt ]< text>

可是,这个凯撒加密法脚本太长了(39 行,其间乃至还没包括加密代码自身),并且很难读懂。

解析指令行参数应该还有更好的办法……

试试 argparse?

argparse 是 Python 用来解析指令行参数的规范库。

咱们来看看用 argparse 怎样编写凯撒加密的脚本:

import argparse

fromcaesar_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

ifargs.decrypt:

key = -key

cyphertext = encrypt(text_string, key)

print(cyphertext)

if__name__ == '__main__':

caesar()

这段代码也遵从了上述规矩,并且与前面的手艺编写的脚本比较,能够供给更精确的文档,以及更具有交互性的过错处理:

> pythoncaesar__using_argparse.py--encodeMymessage

usage: caesar__using_argparse.py[-h][-e | -d][-k KEY][text [text ...]]

caesar__using_argparse.py: error: unrecognizedarguments: --encode

> pythoncaesar__using_argparse.py--help

usage: caesar__using_argparse.py[-h][-e | -d][-k KEY][text [text ...]]

positional arguments:

text

optionalarguments:

-h, --help show this help message andexit

-e, --encrypt

-d, --decrypt

-k KEY, -- keyKEY

可是,细心看了这段代码后,我发现(尽管有点片面)函数最初的几行(从7行到13行)界说了参数,但界说办法并不太高雅:它太臃肿了,并且彻底是程式化的。应该有更描述性、更简练的办法。

click 能做得更好!

走运的是,有个 Python 库能供给与 argparse 相同的功用(乃至还能供给更多),它的代码风格更高雅。这个库的姓名叫 click。

这儿是凯撒加密脚本的第三版,运用了 click:

importclick

fromcaesar_encryption importencrypt

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

ifdecrypt:

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

fromcaesar_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):

ifinput_file:

text= input_file.read()

else:

text= click.prompt( 'Enter a text', hide_input=not decrypt)

ifdecrypt:

key= - key

cyphertext = encrypt( text, key)

ifoutput_file:

output_file.write(cyphertext)

else:

click.echo(cyphertext)

if__name__ == '__main__':

caesar()

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

  • 首要,留意到我给每个参数选项都加了个help参数。由于脚本变得复杂了,help参数能够给脚本的行为增加一些文档。运转成果如下:

> python caesar__v2.py --help

Usage: caesar__v2.py [OPTIONS]

Options:

--input_file FILENAME File inwhich there isthe textyou want toencrypt/decrypt. Ifnotprovided, a prompt will allow you totype the input text.

--output_file FILENAME File inwhich the encrypted/decrypted textwill be written. Ifnotprovided, the output textwill just be printed.

-d, --decrypt / -e, --encrypt Whether you want toencrypt the input textordecrypt it.

-k, -- keyINTEGERThe numeric keytouse forthe caesar encryption / decryption.

--help Show this message andexit.

  • 两个新的参数:input_file 和 output_file,类型均为 click.File。该库能够用正确的形式翻开文件,处理或许的过错,再履行函数。例如:

> python caesar__v2.py --decrypt --input_file wrong_file.txt

Usage: caesar__v2.py [OPTIONS]

Error: Invalid value for"--input_file": Could notopenfile: wrong_file.txt: No such file ordirectory

  • 正像help文本中解说的那样,假如没有供给input_file,就运用click.promp让用户直接在提示符下输入文本,在加密形式下这些文本是躲藏的。如下所示:

> python caesar__v2.py --encrypt --key 2

Enter a text: **************

yyy.ukectc.eqo

破解密文!

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

最简略的战略就是用一切或许的秘钥调用解密函数 25 次,阅览解密成果,看看哪个是合理的。

但你很聪明,并且也很懒,所以你想让整个进程自动化。断定解密后的 25 个文本哪个最或许是原始文本的办法之一,就是计算一切这些文本中的英文单词的个数。这能够运用 PyEnchant 模块完结:

importclick

importenchant

fromcaesar_encryption importencrypt

@click.command()

@click.option(

'--input_file',

type=click.File( 'r'),

required= True,

)

@click.option(

'--output_file',

type=click.File( 'w'),

required= True,

)

defcaesar_breaker(input_file, output_file):

cyphertext = input_file.read()

english_dictionnary = enchant.Dict( "en_US")

max_number_of_english_words = 0

forkey inrange( 26):

plaintext = encrypt(cyphertext, -key)

number_of_english_words = 0

forword inplaintext.split( ' '):

ifword andenglish_dictionnary.check(word):

number_of_english_words += 1

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

形似运转得很不错,但别忘了,好的指令行程序还有个规矩需求恪守:

4.A 不是当即完结的使命应当显现进度条。

示例中的文本包括10^4个单词,因而该脚本需求大约5秒才干解密。这很正常,由于它需求查看一切25个秘钥,每个秘钥都要查看10^4个单词是否呈现在英文字典中。

假定你要解密的文本包括10^5个但IC,那么就要花费50秒才干输出成果,用户或许会十分着急。

因而我主张这种使命一定要显现进度条。特别是,显现进度条还十分简单完结。

下面是个显现进度条的比如:

importclick

importenchant

fromtqdm importtqdm

fromcaesar_encryption importencrypt

@click.command()

@click.option(

'--input_file',

type=click.File( 'r'),

required= True,

)

@click.option(

'--output_file',

type=click.File( 'w'),

required= True,

)

defcaesar_breaker(input_file, output_file):

cyphertext = input_file.read()

english_dictionnary = enchant.Dict( "en_US")

best_number_of_english_words = 0

forkey intqdm(range( 26)):

plaintext = encrypt(cyphertext, -key)

number_of_english_words = 0

forword inplaintext.split( ' '):

ifword andenglish_dictionnary.check(word):

number_of_english_words += 1

ifnumber_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 是 Python 库的姓名,也是它包括的类的姓名。只需用它包裹一个可迭代的东西,就能显现出进度条:

forkeyintqdm(range( 26)):

这样就能显现出十分美丽的进度条。我都不敢相信这是真的。

别的,click也供给相似的显现进度条的东西(click.progress_bar),但我觉得它的外观不太简单懂,并且要写的代码也多一些。

我期望这篇文章能让你在改善开发者的体会上多花点时刻。

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

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

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