Python程序员的主要工作是编写命令行程序,直接在终端运行的脚本。随着项目规模的增长,我们希望创建一个有效的命令行界面,通过提供不同的参数来解决不同的问题,而不是每次都修改源代码。为了实现这个目标,我总结了四个原则,希望对大家有所帮助:命令行参数要提供默认值处理所有可能的参数错误,包括参数缺失、数据类型错误、文件找不到等编写全面的文档,解释参数的含义以及如何设置带有进度条的长时间运行的任务一个简单的例子让我们将这些规则应用到一个具体的案例中:一个使用凯撒密码加密和解密消息的脚本。假设我们编写了一个加密函数,如下所示。现在创建一个脚本来加密和解密消息。该脚本允许用户选择:模式(加密或解密)、密钥。前者的默认值为加密,后者的默认值为1。这都是通过命令行参数实现的。defencrypt(plaintext,key):cyphertext=''对于明文中的字符:ifcharacter.isalpha():number=ord(character)number+=keyifcharacter.isupper():ifnumber>ord('Z'):number-=26elifnumberord('z'):number-=26elifnumberpythoncaesar_script.py--key23--decryptmysecretmessagepbvhfuhwphvvdjhsys.argvlistincludes:['caesar_script.py','--key','23','--decrypt','my','secret','message']为了得到参数值,需要遍历参数列表,寻找'--key'(或'-k')得到键值,并查找“--decrypt”获取模式。importsysfromcaesar_encryptionimportencryptdefcaesar():key=1is_error=Falseforindex,arginenumerate(sys.argv):ifargin['--key','-k']和len(sys.argv)>index+1:key=int(sys.argv[index+1])delsys.argv[index]delsys.argv[index]breakforindex,arginenumerate(sys.argv):ifargin['--encrypt','-e']:delsys.argv[index]breakifargin['--decrypt','-d']:key=-keydelsys.argv[index]breakiflen(sys.argv)==1:is_error=Trueelse:forarginsys.argv:ifarg.startswith('-'):is_error=Trueifis_error:print(f'Usage:python{sys.argv[0]}[--key][--encrypt|decrypt]')else:print(encrypt(''.join(sys.argv[1:]),key))if__name__=='__main__':caesar()代码遵循我们一开始提出的原则:有一个默认键和一个默认模式来处理基本错误(Noinputtextorunknownarguments)在错误的参数上或在不带参数的情况下调用脚本时打印简明消息这个版本的脚本比较长(39行,不包括加密函数),而且代码很丑有没有更好的方法来解析命令行参数?输入argparseargparse是用于解析命令行参数的Python标准库模块。修改脚本以使用argparse解析命令行参数: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.keyifargs.decrypt:key=-keycyphertext=encrypt(text_string,key)print(cyphertext)if__name__=='__main__':caesar()代码仍然遵循我们提出的原则,提供了比手动解析命令行参数更精确的文档和更具交互性的错误处理。>pythoncaesar_script_using_argparse.py--encode我的消息用法:caesar_script_using_argparse.py[-h][-e|-d][-kKEY][text[text...]]caesar_script_using_argparse.py:错误:无法识别的参数:--编码>pythoncaesar_script_using_argparse.py--helpusage:caesar_script_using_argparse.py[-h][-e|-d][-kKEY][text[text...]]位置参数:text可选参数:-h,--help显示此帮助信息并退出-e,--encrypt-d,--decrypt-kKEY,--keyKEY脚本的第7到13行定义了命令行参数,但它们不是很优雅:太冗长了,而在编程上,我们可以用更紧凑和声明的方式来完成。使用click创建更好的命令行界面。幸运的是,有一个用于创建命令行界面的三方库点击。它不仅提供了比argparse更多的功能,而且代码风格也更漂亮。将argparse换成click,继续优化脚本。importclickfromcaesar_encryptionimportencrypt@click.command()@click.argument('text',nargs=-1)@click.option('--decrypt/--encrypt','-d/-e')@click.option('--key','-k',default=1)defcaesar(text,decrypt,key):text_string=''.join(text)如果解密:key=-keycyphertext=encrypt(text_string,key)click.echo(cyphertext)if__name__=='__main__':caesar()请注意,命令行参数和选项是在装饰器中声明的,这使得它们可以作为函数参数直接访问。我们仔细剖析一下上面的代码:nargs定义了命令行参数接收的值的个数,默认值为1,nargs=-1允许提供任意个数的单词。--encrypt/--decrypt定义最终作为布尔值传递给程序的互斥选项。click.echo是click库提供的基础函数。它的功能和print类似,但是提供了更强大的功能,比如调整打印到控制台的文本的颜色。从本地文件读取输入命令行参数接收的值是将被加密的绝密消息,因此要求用户直接在终端中输入纯文本可能会引起安全问题。更安全的方法是使用隐藏提示,或者从本地文件中读取文本,这对于长文本更实用。同样的想法也适用于输出:用户可以将其保存到文件中,或者在终端中打印出来。让我们继续优化脚本。importclickfromcaesar_encryptionimportencrypt@click.command()@click.option('--input_file',type=click.File('r'),help='文件中有你要加密/解密的文本。''如果未提供,提示将允许您键入输入文本。',)@click.option('--output_file',type=click.File('w'),help='加密文件所在的文件/将写入解密的文本。''如果未提供,将只打印输出文本。',)@click.option('--decrypt/--encrypt','-d/-e',help='是否要加密或解密输入文本。')@click.option('--key','-k',default=1,help='用于凯撒加密/解密的数字密钥。')defcaesar(input_file,output_file,decrypt,key):ifinput_file:text=input_file.read()else:text=click.prompt('Enteratext',hide_input=notdecrypt)ifdecrypt:key=-key密文=加密(文本,密钥)ifoutput_file:output_file.write(cyphertext)else:click.echo(cyphertext)if__name__=='__main__':caesar()随着脚本变得越来越复杂,我们创建参数文档(通过定义click.option装饰器的帮助参数实现),详细解释参数的作用,效果如下>pythoncaesar_script_v2.py--helpUsage:caesar_script_v2.py[OPTIONS]Options:--input_fileFILENAME文件中有你要加密的文本/解密。如果未提供,将提示您键入输入文本。--output_fileFILENAME将写入加密/解密文本的文件。如果未提供,将仅打印输出文本。-d,--decrypt/-e,--encrypt是否要加密或解密输入文本。-k,--keyINTEGER用于凯撒加密/解密的数字密钥。--help显示此消息并退出。我们有两个新的参数input_file和output_file,类型是click.File,click会以正确的方式打开文件并处理可能出现的错误。例如找不到文件:>pythoncaesar_script_v2.py--decrypt--input_filewrong_file.txtUsage:caesar_script_v2.py[OPTIONS]Error:Invalidvaluefor"--input_file":Couldnotopenfile:wrong_file.txt:Nosuchfile或者directory如果不提??供input_file,我们使用click.prompt在命令行创建一个提示窗口,让用户直接输入文本,对于加密模式会隐藏提示。效果如下:>pythoncaesar_script_v2.py--encrypt--key2输入一段文字:******************yyy.ukectc.eqo假设你是一名黑客:想要解密加密的密文,但您不知道密钥是什么。最简单的策略是使用所有可能的密钥调用解密函数25次,读取解密结果,看看哪个是有意义的。但是你很聪明,而且你很懒惰,所以你想使整个过程自动化。确定25个解密文本中哪一个最有可能是原始文本的一种方法是计算所有文本中的英文单词数量。这可以使用PyEnchant模块实现:importclickimportenchantfromcaesar_encryptionimportencrypt@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(26):plaintext=encrypt(cyphertext,-key)number_of_english_words=0forwordinplaintext.split(''):ifwordandenglish_dictionnary.check(word):number_of_english_words+=1ifnumber_of_english_words>max_number_of_english_words:max_number_of_english_words=number_of_english_wordsbest_plaintext=plaintextbest_key=keyclick.echo(f'最有可能的加密密钥是{best_key}。它给出以下明文:\n\n{best_plaintext[:1000]}...')output_file.write(best_plaintext)if__name__=='__main__':caesar_breaker()使用进度条的示例中的文本包含10^4个单词,因此脚本大约需要5秒toDecrypting这个没问题,因为它需要检查所有25个密钥,每个密钥检查10^4个单词是否出现在英语词典中。假设你要解密的文本包括10^5个单词,输出结果需要50秒,用户可能会很着急。所以我建议这种任务一定要显示进度条。特别是显示一个进度条也很容易实现。下面是一个显示进度条的例子:importclickimportenchantfromtqdmimporttqdmfromcaesar_encryptionimportencrypt@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对于tqdm(range(26))中的密钥:plaintext=encrypt(cyphertext,-key)number_of_english_words=0forwordinplaintext.split(''):ifwordandenglish_dictionnary.check(word):number_of_english_words+=1ifnumber_of_english_words>best_number_of_english_words:best_number_of_english_words=number_of_english_wordsbest_plaintext=plaintextbest_key=keyclick.echo(f'Themostlikelyencryptionkeyis{best_key}.它给出了以下内容欠明文:\n\n{best_plaintext[:1000]}...')output_file.write(best_plaintext)if__name__=='__main__':caesar_breaker()这里使用了tqdm库,tqdm.tqdm类可以将任何迭代对象转换为进度条。click也提供了一个类似的界面来创建进度条(click.progress_bar),但我认为它不如tqdm好用。