python之强大的命令行工具click
什么是click
click是flask的开发团队pallets的另一款开源项目,用于快速创建命令行工具的模块。
git:https://github.com/pallets/click
文档:https://click.palletsprojects.com/en/8.1.x/
quickStart
click的思路非常简单。
-
定义处理函数,通过@click.command()装饰一个函数,使之成为命令行接口。 -
并且通过@click.option()、@click.arguments等装饰器装饰函数,为其添加命令行选项、参数等。
import click
@click.command()
@click.option('--count', default=1, help='Number of greetings.')
@click.option('--name', prompt='Your name',
help='The person to greet.')
def hello(count, name):
"""Simple program that greets NAME for a total of COUNT times."""
for x in range(count):
click.echo(f"Hello {name}!")
if __name__ == '__main__':
hello()
在上面的示例中,使用@click.command()装饰函数hello,使之成为命令行接口。使用click.option来定义选项:--count和—-name。
执行代码,指定count和name:
% python3 test.py --count=2 --name=xixi
Hello xixi!
Hello xixi!
我们还可以通过 --help 参数查看自动生成的帮助信息:
% python3 test.py --help
Usage: test.py [OPTIONS]
Simple program that greets NAME for a total of COUNT times.
Options:
--count INTEGER Number of greetings.
--name TEXT The person to greet.
--help Show this message and exit.
click把命令行分为3个组成部分:参数、选项和命令。
-
参数:参数就是在命令后的除选项外的内容。比如git add a.txt中的a.txt就是表示文件路径的参数 -
选项:就是以-或—开头的命令。如-f或--file。 -
命令:即命令行程序的本质,比如git是命令、git add中的add是git的子命令。
接下来,看下click是怎么帮助开发人员定义参数、选项和命令的。
定义参数
看个demo
import click
@click.command()
@click.argument('x')
@click.argument('y')
def hello(x, y):
print(x, y)
if __name__ == '__main__':
hello()
执行代码
% python3 test.py 1 2
1 2
可以看到可以使用@click.argument来定义参数,第一个参数为参数名。参数默认情况下字符串类型,我们可以通过type来指定参数类型。
import click
@click.command()
@click.argument('x', type=click.INT)
@click.argument('y', type=click.INT)
def hello(x, y):
print(x + y)
if __name__ == '__main__':
hello()
"""
执行结果
% python3 test.py 1 2
3
"""
click 支持的多种参数类型:
-
str / click.STRING 表示字符串类型,这也是默认类型 -
int / click.INT 表示整型 -
float / click.FLOAT 表示浮点型 -
bool / click.BOOL 表示布尔型。对于 1、yes、y 和 true 会转化为 True;0、no、n 和 false 会转化为 False -
click.UUID 表示 UUID,会自动将参数转换为 uuid.UUID 对象 -
click.FILE 表示文件,会自动将参数转换为文件对象,并在命令行结束时自动关闭文件 -
click.PATH 表示路径 -
click.Choice 表示选择选项 -
click.IntRange 表示范围选项
除此之外,还支持自定义参数类型。click定义自定义类型,需要编写click.ParamType的子类,并重载convert方法。我们看一个在celery中使用的自定义参数类型的例子。
class App(ParamType):
"""Application option."""
name = "application"
def convert(self, value, param, ctx):
try:
return find_app(value)
except ModuleNotFoundError as e:
if e.name != value:
exc = traceback.format_exc()
self.fail(
UNABLE_TO_LOAD_APP_ERROR_OCCURRED.format(value, exc)
)
self.fail(UNABLE_TO_LOAD_APP_MODULE_NOT_FOUND.format(e.name))
except AttributeError as e:
attribute_name = e.args[0].capitalize()
self.fail(UNABLE_TO_LOAD_APP_APP_MISSING.format(attribute_name))
except Exception:
exc = traceback.format_exc()
self.fail(
UNABLE_TO_LOAD_APP_ERROR_OCCURRED.format(value, exc)
)
APP = App()
我们在使用celery启动worker时会需要通过-A指定app, 那么这个app的类型就是这里的App(),其convert方法中,包含对app是否存在,是否能加载的校验(注:这里的-A其实属于option了,但是含义是相似的)。
定义选项
我们可以通过@click.option可以给命令增加选项,并通过配置函数的参数来配置不同功能的选项。
Click.option接收的前两个参数是长、短选项(顺序随意)。
-
长选项以“—”开头,例如“—file”。 -
短选项以“-”开头,例如“-s”。
第三个参数为选项参数的名称。
import click
@click.command()
@click.option('-s', '--string-to-echo', 'string')
def echo(string):
click.echo(string)
if __name__ == '__main__':
echo()
"""
% python3 test.py -s test
test
"""
如果不指定第三个参数,将会使用长选项的下划线形式名称:
import click
@click.command()
@click.option('-s', '--string-to-echo')
def echo(string_to_echo):
click.echo(string_to_echo)
if __name__ == '__main__':
echo()
选择项的type和上面介绍的参数type类似。
定义命令和嵌套命令
通过上文,我们已经知道可以使用@click.command来非常简单地定义一个命令。然而click真正强大的地方在于其借助@click.group实现的命令组的功能。所谓命令组就是若干个命令(或叫子命令)的集合。
import click
@click.group()
def cli():
pass
@click.command()
def initdb():
click.echo('Initialized the database')
@click.command()
def dropdb():
click.echo('Dropped the database')
cli.add_command(initdb)
cli.add_command(dropdb)
if __name__ == '__main__':
cli()
如上所示,@click.group装饰器的工作方式类似于@click.command装饰器,但它创建了一个group对象,该对象可以被赋予多个子命令。这些子命令可以通过group.add_command附加。
除此之外,也可以使用Group.command装饰器来自动附加和创建命令。
import click
@click.group()
def cli():
pass
@cli.command() #注意这里是 @cli.command(), 不是@click.command()!
def initdb():
click.echo('Initialized the database')
@cli.command() #注意这里是 @cli.command(), 不是@click.command()!
def dropdb():
click.echo('Dropped the database')
# cli.add_command(initdb)
# cli.add_command(dropdb)
if __name__ == '__main__':
cli()
上下文传递
默认情况下,命令组接收的参数和子命令彼此独立,但有时候我们希望可以在子命令中获取命令组的参数,这时候就可以用Context来实现。具体是通过pass_context装饰器来显式地让click传递上下文,这时候,context会作为第一个参数进行传递。
import click
@click.group()
@click.option('--debug/--no-debug', default=False)
@click.pass_context
def cli(ctx, debug):
# 确保 ctx.obj 存在并且是个 dict。 (以防 `cli()` 指定 obj 为其他类型
ctx.ensure_object(dict)
ctx.obj['DEBUG'] = debug
@cli.command()
@click.pass_context
def sync(ctx):
click.echo('Debug is %s' % (ctx.obj['DEBUG'] and 'on' or 'off'))
if __name__ == '__main__':
cli(obj={})
默认情况下,调用子命令的时候才会调用命令组,否则会报“Error: Missing command.”,而有时我们可能想直接调用命令组,通过指定 click.group的invoke_without_command=True 来实现:
@click.group(invoke_without_command=True)
总结
通过上文的介绍,我们已经可以通过定义参数、选项和命令来轻松地实现一个基本的命令行程序。此外,click还提供了许多buffer功能,比如参数自动补全、支持运行时延迟加载子命令等等,感兴趣可以去官网一探究竟。
参考文档:https://zhuanlan.zhihu.com/p/90307978 https://blog.csdn.net/weixin_38278993/article/details/100052961 https://click.palletsprojects.com/en/8.1.x/