一、前言在过去半年的Python命令行之旅中,我们依次学习了argparse、docopt、click和fire库的特性和用法,逐渐理解了设计理念Python命令行库和演变。作为本次旅程的结束,本文希望能从更高的角度对这些库进行横向比较,总结它们的异同和使用场景,以便在处理不同场景时分析优劣,选择合适的供您自己使用的图书馆。本系列文章默认使用Python3作为解释器。如果你还在使用Python2,请注意两者在语法和库使用上的差异~2.设计理念在讨论各个库的设计理念之前,我们先来设计一个计算器程序。事实上,这个例子在argparse库中。它出现在第一个解释中,即:命令行程序接受一个位置参数,可以出现多次,是一个数字。默认情况下,命令行程序将查找给定数字字符串的最大值。如果指定Option参数--sum,那么它会计算给定的一串数字,希望从各个库中实现这个例子的代码中进一步理解他们的设计理念。2.1、argparseargparse的设计理念是为你提供最细粒度的控制。你需要详细告诉它参数是选项参数还是位置参数,参数值的类型是什么,参数的处理动作是什么。简而言之,它就像一个没有智能分析能力的第一代机器人。你需要告诉它明确的信息,它会根据给定的信息帮助你做事。以下示例是一个由argparse实现的计算器程序:importargparse#1。设置解析器parser=argparse.ArgumentParser(description='CalculatorProgram.')#2。定义参数#添加位置参数nums,在帮助信息中显示为num#其他类型为int,支持多个输入,至少有一个parser.add_argument('nums',metavar='num',type=int,nargs='+',help='anumfortheaccumulator')#添加选项参数--sum,参数由解析器解析,对应的属性名是accumulate#如果不提供--sum,默认值为max函数,否则为求和函数#3。解析参数args=parser.parse_args(['--sum','1','2','3'])print(args)#Result:Namespace(accumulate=,nums=[1,2,3])#4。业务逻辑result=args.accumulate(args.nums)print(result)#根据上面的['--sum','1','2','3']参数,accumulate是求和函数,而结果是6。从上面的例子可以看出,我们需要通过add_argument很清楚的告诉argparse这个参数长什么样子:是位置参数nums,还是选项参数--sum它的类型是什么,比如type=int表示类型是int这个参数可以重复几次,比如nargs='+'表示至少提供1个参数来存储一些东西,比如action='store_const'表示存储一个常量,然后解析根据给定的元信息(即['--sum',示例中的'1','二十三'])的命令行参数。这是非常计算性的思维,虽然冗长,但也带来了灵活性。2.2.docopt从argparse的概念可以看出是命令式的。这时候docopt就另辟蹊径了。声明式方法也可以吗?一个命令行程序的帮助信息其实包含了命令行完整的元信息,那么是不是可以通过定义帮助信息来定义命令行呢?docopt就是基于这个想法来设计的。声明式的好处是只要掌握了声明式语法,定义命令行的元信息就会非常简单。下面的例子是一个由docopt实现的计算器程序:#1。定义接口描述/帮助信息"""CalculatorProgram.Usage:calculator.py[--sum]...calculator.py(-h|--help)Options:-h--helpShowhelp.--sumSumthenums(默认值:findthemax)。“”“来自docoptimportdocopt#2。解析命令行arguments=docopt(__doc__,options_first=True,argv=['--sum','1','2','3'])print(arguments)#Result:{'--help':False,'--sum':True,'':['1','2','3']}#3。业务逻辑nums=(int(num)fornuminarguments[''])ifarguments['--sum']:result=sum(nums)else:result=max(nums)print(result)#基于['--sum','1','2','3']参数上面,处理函数是求和函数,结果为6。从上面的例子可以看出,我们通过__doc__这样定义相当于argparse中的add_argument,然后docopt会根据这个元信息将命令行参数转换成字典。这个字典需要在业务逻辑中处理。与argparse的比较:对于更复杂的命令程序,docopt在元信息的定义上会更简单。但是在业务逻辑处理上,由于argparse在处理一些简单的参数(比如例子中的情况)时会更方便,相对来说,docopt转成字典后,手上会更复杂对业务逻辑的所有处理。2.3.click命令行程序本质上是定义参数和处理参数,处理参数的逻辑必须和定义的参数相关。.能否通过函数和装饰器来实现处理参数逻辑和定义参数的关联?而click就是这样设计的。click使用装饰器的好处是,装饰器优雅的语法将参数定义和处理逻辑融为一体,从而隐含了路由关系。相比于argparse和docopt,自己对解析出来的参数做路由关系要简单的多。下面的例子是click实现的计算器程序:importsysimportclicksys.argv=['calculator.py','--sum','1','2','3']#2。定义参数@click.command()@click.argument('nums',nargs=-1,type=int)@click.option('--sum','use_sum',is_flag=True,help='sumthenums(default:findthemax)')#1.Businesslogicdefcalculator(nums,use_sum):"""CalculatorProgram."""print(nums,use_sum)#Output:(1,2,3)Trueifuse_sum:result=sum(nums)else:result=max(nums)print(result)#根据上面的['--sum','1','2','3']参数,处理函数为求和函数,结果为6calculator()从上面的例子可以看出,参数和对应的处理逻辑很好的绑定在一起,看起来很直观,让我们可以很清楚的了解到参数会被如何处理,这一点在使用时尤为重要有大量的参数。这里是click相比argparse和docopt最明显的优势。此外,click还内置了很多实用工具和附加功能,比如Bash补全、颜色、分页支持、进度条等诸多实用功能,可谓如虎添翼。2.4.Firefire使用通用的面向对象的方式来玩命令行。这样的对象可以是类、函数、字典、列表等,更加灵活简单。你不需要定义参数类型,fire会根据输入和参数默认值自动判断,这无疑进一步简化了实现过程。下面的例子是fire实现的一个计算器程序:importsysimportfiresys.argv=['calculator.py','1','2','3','--sum']builtin_sum=sum#1.Businesslogic#sum=False,暗示它是一个选项参数--sum,当不提供时,它是False#*nums暗示它是一个位置参数,可以提供任意数字defcalculator(sum=False,*nums):"""CalculatorProgram."""print(sum,nums)#Output:True(1,2,3)ifsum:result=builtin_sum(nums)else:result=max(nums)print(result)#根据以上['1','2','3','--sum']参数,处理函数为sum函数,结果为6fire.Fire(calculator)从上面的例子我们可以看出,提供的方法fire无疑是最简单也是最Pythonic的。我们只需要关注业务逻辑,命令行参数的定义与函数参数的定义集成在一起。但是,有优点也有缺点。比如nums并没有说它是什么类型,也就是说输入的字符串'abc'也是合法的,也就是说一个严格的命令行程序在自身的业务逻辑中必须符合预期。类型约束。3、横向比较最后,横向比较一下argparse、docopt、click、fire库的功能和特点:Python的命令行库种类繁多,各有特点。结合上面的总结,就可以选择符合使用场景的库了。如果有几个库在排队,那就根据自己喜欢的风格来选择吧。这些库都很优秀,背后的思想值得学习和拓展。