2023年6月29日发(作者:)
Python命令⾏之旅:深⼊click之⼦命令篇作者:HelloGitHub-ProdesireHelloGitHub 的《讲解开源项⽬》系列,项⽬地址:⼀、前⾔在上两篇⽂章中,我们介绍了
click 中的”参数“和“选项”,本⽂将继续深⼊了解
click,着重讲解它的“命令”和”组“。本系列⽂章默认使⽤ Python 3 作为解释器进⾏讲解。若你仍在使⽤ Python 2,请注意两者之间语法和库的使⽤差异哦~⼆、命令和组Click 中⾮常重要的特性就是任意嵌套命令⾏⼯具的概念,通过 和 (实际上是 )来实现。所谓命令组就是若⼲个命令(或叫⼦命令)的集合,也成为多命令。2.1 回调调⽤对于⼀个普通的命令来说,回调发⽣在命令被执⾏的时候。如果这个程序的实现中只有命令,那么回调总是会被触发,就像我们在上⼀篇⽂章中举出的所有⽰例⼀样。不过像
--help 这类选项则会阻⽌进⼊回调。对于组和多个⼦命令来说,情况略有不同。回调通常发⽣在⼦命令被执⾏的时候:@()@('--debug/--no-debug', default=False)def cli(debug): ('Debug mode is %s' % ('on' if debug else 'off'))@d() # @cli, not @click!def sync(): ('Syncing')执⾏效果如下:Usage: [OPTIONS] COMMAND [ARGS]...Options: --debug / --no-debug --help Show this message and ds: sync$ --debug syncDebug mode is onSyncing在上⾯的⽰例中,我们将函数
cli 定义为⼀个组,把函数
sync 定义为这个组内的⼦命令。当我们调⽤
--debug sync 命令时,会依次触发
cli 和
sync 的处理逻辑(也就是命令的回调)。2.2 嵌套处理和上下⽂从上⾯的例⼦可以看到,命令组
cli 接收的参数和⼦命令
sync 彼此独⽴。但是有时我们希望在⼦命令中能获取到命令组的参数,这就可以⽤ 来实现。每当命令被调⽤时,click 会创建新的上下⽂,并链接到⽗上下⽂。通常,我们是看不到上下⽂信息的。但我们可以通过 装饰器来显式让
click 传递上下⽂,此变量会作为第⼀个参数进⾏传递。@()@('--debug/--no-debug', default=False)@_contextdef cli(ctx, debug): # 确保 存在并且是个 dict。 (以防 `cli()` 指定 obj 为其他类型 _object(dict) ['DEBUG'] = debug@d()@_contextdef sync(ctx): ('Debug is %s' % (['DEBUG'] and 'on' or 'off'))if __name__ == '__main__': cli(obj={})在上⾯的⽰例中:通过为命令组
cli 和⼦命令
sync 指定装饰器
_context,两个函数的第⼀个参数都是
ctx 上下⽂在命令组
cli 中,给上下⽂的
obj 变量(字典)赋值在⼦命令
sync 中通过
['DEBUG'] 获得上⼀步的参数通过这种⽅式完成了从命令组到⼦命令的参数传递2.3 不使⽤命令来调⽤命令组默认情况下,调⽤⼦命令的时候才会调⽤命令组。⽽有时你可能想直接调⽤命令组,通过指定
的
invoke_without_command=True 来实现:@(invoke_without_command=True)@_contextdef cli(ctx): if d_subcommand is None: ('I was invoked without subcommand') else: ('I am about to invoke %s' % d_subcommand)@d()def sync(): ('The subcommand')调⽤命令有:$ toolI was invoked without subcommand$ tool syncI am about to invoke syncThe subcommand在上⾯的⽰例中,通过
d_subcommand 来判断是否由⼦命令触发,针对两种情况打印⽇志。2.4 ⾃定义命令组/多命令除了使⽤ 来定义命令组外,你还可以⾃定义命令组(也就是多命令),这样你就可以延迟加载⼦命令,这会很有⽤。⾃定义多命令需要实现
list_commands 和
get_command ⽅法:import clickimport osplugin_folder = (e(__file__), 'commands')class MyCLI(ommand): def list_commands(self, ctx): rv = [] # 命令名称列表 for filename in r(plugin_folder): if th('.py'): (filename[:-3]) () return rv def get_command(self, ctx, name): ns = {} fn = (plugin_folder, name + '.py') # 命令对应的 Python ⽂件 with open(fn) as f: code = compile((), fn, 'exec') eval(code, ns, ns) return ns['cli']cli = MyCLI(help='This tool's subcommands are loaded from a ' 'plugin folder dynamically.')# 等价⽅式是通过 d 装饰器,指定 cls=MyCLI# @d(cls=MyCLI)# def cli():# passif __name__ == '__main__': cli()2.5 合并命令组/多命令当有多个命令组,每个命令组中有⼀些命令,你想把所有的命令合并在⼀个集合中时,dCollection 就派上了⽤场:@()def cli1(): pass@d()def cmd1(): """Command on cli1"""@()def cli2(): pass@d()def cmd2(): """Command on cli2"""cli = dCollection(sources=[cli1, cli2])if __name__ == '__main__': cli()调⽤命令有:$ cli --helpUsage: cli [OPTIONS] COMMAND [ARGS]...Options: --help Show this message and ds: cmd1 Command on cli1 cmd2 Command on cli2从上⾯的⽰例可以看出,cmd1 和
cmd2 分别属于
cli1 和
cli2,通过
dCollection 可以将这些⼦命令合并在⼀起,将其能⼒提供个同⼀个命令程序。Tips:如果多个命令组中定义了同样的⼦命令,那么取第⼀个命令组中的⼦命令。2.6 链式命令组/多命令有时单级⼦命令可能满⾜不了你的需求,你甚⾄希望能有多级⼦命令。典型地,setuptools 包中就⽀持多级/链式⼦命令:
sdist bdist_wheel upload。在 click 3.0 之后,实现链式命令组变得⾮常简单,只需在
中指定
chain=True:@(chain=True)def cli(): pass@d('sdist')def sdist(): ('sdist called')@d('bdist_wheel')def bdist_wheel(): ('bdist_wheel called')调⽤命令则有:$ sdist bdist_wheelsdist calledbdist_wheel called2.7 命令组/多命令管道链式命令组中⼀个常见的场景就是实现管道,这样在上⼀个命令处理好后,可将结果传给下⼀个命令处理。实现命令组管道的要点是让每个命令返回⼀个处理函数,然后编写⼀个总的管道调度函数(并由
callback() 装饰):@(chain=True, invoke_without_command=True)@('-i', '--input', type=('r'))def cli(input): pass@callback()def process_pipeline(processors, input): iterator = (('rn') for x in input) for processor in processors: iterator = processor(iterator) for item in iterator: (item)@d('uppercase')def make_uppercase(): def processor(iterator): for line in iterator: yield () return processor@d('lowercase')def make_lowercase(): def processor(iterator): for line in iterator: yield () return processor@d('strip')def make_strip(): def processor(iterator): for line in iterator: yield () return processor在上⾯的⽰例中:将
cli 定义为了链式命令组,并且指定 invoke_without_command=True,也就意味着可以不传⼦命令来触发命令组定义了三个命令处理函数,分别对应
uppercase、lowercase 和
strip 命令在管道调度函数
process_pipeline 中,将输⼊
input 变成⽣成器,然后调⽤处理函数(实际输⼊⼏个命令,就有⼏个处理函数)进⾏处理2.8 覆盖默认值默认情况下,参数的默认值是从通过装饰器参数
default 定义。我们还可以通过
t_map 上下⽂字典来覆盖默认值:@()def cli(): pass@d()@('--port', default=8000)def runserver(port): ('Serving on 127.0.0.1:%d/' % port)if __name__ == '__main__': cli(default_map={ 'runserver': { 'port': 5000 } })在上⾯的⽰例中,通过在
cli 中指定
default_map 变可覆盖命令(⼀级键)的选项(⼆级键)默认值(⼆级键的值)。我们还可以在
中指定
context_settings 来达到同样的⽬的:CONTEXT_SETTINGS = dict( default_map={'runserver': {'port': 5000}})@(context_settings=CONTEXT_SETTINGS)def cli(): pass@d()@('--port', default=8000)def runserver(port): ('Serving on 127.0.0.1:%d/' % port)if __name__ == '__main__': cli()调⽤命令则有:$ cli runserverServing on 127.0.0.1:5000/三、总结本⽂⾸先介绍了命令的回调调⽤、上下⽂,再进⼀步介绍命令组的⾃定义、合并、链接、管道等功能,了解到了
click 的强⼤。⽽命令组中更加⾼阶的能⼒()则可看官⽅⽂档进⼀步了解。我们通过介绍
click 的参数、选项和命令已经能够完全实现命令⾏程序的所有功能。⽽
click 还为我们提供了许多锦上添花的功能,⽐如实⽤⼯具、参数⾃动补全等,我们将在下节详细介绍。『讲解开源项⽬系列』——让对开源项⽬感兴趣的⼈不再畏惧、让开源项⽬的发起者不再孤单。跟着我们的⽂章,你会发现编程的乐趣、使⽤和发现参与开源项⽬如此简单。欢迎留⾔联系我们、加⼊我们,让更多⼈爱上开源、贡献开源~
发布者:admin,转转请注明出处:http://www.yc00.com/xiaochengxu/1687977701a62898.html
评论列表(0条)