作为 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] [ –encrypt|decrypt ]')else: print(encrypt(' '.join(sys.argv[1:]), key))if __name__ ==
'__main__': caesar()
这个脚本遵从了一些咱们前面引荐的规矩:
支撑默许秘钥和默许形式根本的过错处理(没有供给输入文本的状况,以及供给了无法辨认的参数的状况)犯错时或许不带任何参数调用脚本时会显现文档:>
pythoncaesar_script_using_sys_argv.pyUsage: pythoncaesar.py[ –key][
–encrypt|decrypt ]
可是,这个凯撒加密法脚本太长了(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()
这个版别有什么新东西吗?
首要,留意到我给每个参数选项都加了个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. Ifnot provided, a prompt will allow you to type the input text.
–output_file FILENAME File in which the encrypted/decrypted text will be
written. Ifnot provided, the output text will just be printed. -d, –decrypt /
-e, –encrypt Whether you want to encrypt the input textor decrypt it.-k,
–keyINTEGER The numeric keyto use for the caesar encryption / decryption.
–help Show this message andexit.
两个新的参数: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 notopen 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 enchantfrom 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,)defcaesar_breaker(input_file, output_file):
cyphertext = input_file.read() english_dictionnary =
enchant.Dict("en_US")max_number_of_english_words = 0for key in range(26):
plaintext = encrypt(cyphertext, -key) number_of_english_words = 0for word in
plaintext.split(' '):if word and
english_dictionnary.check(word):number_of_english_words += 1if
number_of_english_words > max_number_of_english_words:
max_number_of_english_words = number_of_english_words best_plaintext = plaintext
best_key = keyclick.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 enchantfrom tqdm import tqdmfrom 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,)defcaesar_breaker(input_file, output_file): cyphertext =
input_file.read() english_dictionnary = enchant.Dict("en_US")
best_number_of_english_words = 0for key in tqdm(range(26)): plaintext =
encrypt(cyphertext, -key)number_of_english_words = 0for word in
plaintext.split(' '):if word and english_dictionnary.check(word):
number_of_english_words += 1if 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 库的姓名,也是它包括的类的姓名。只需用它包裹一个可迭代的东西,就能显现出进度条:
forkeyin tqdm(range(26)):
这样就能显现出十分美丽的进度条。我都不敢相信这是真的。
别的,click也供给相似的显现进度条的东西(click.progress_bar),但我觉得它的外观不太简单懂,并且要写的代码也多一些。
我期望这篇文章能让你在改善开发者的体会上多花点时刻。