python - Python根据参数数量单击不同的命名参数
问题描述
如何使用 Python click库实现以下概要?
Usage: app CMD [OPTIONS] [FOO] [BAR]
app CMD [OPTIONS] [FOOBAR]
我不知道是否能够根据给定参数的数量为同一命令传递两组不同的命名参数。也就是说,如果只传递了一个参数,则为foobar,但如果传递了两个参数,则为foo和bar。
这种实现的代码表示看起来像这样(前提是你可以使用函数重载,但你不能)
@click.command()
@click.argument('foo', required=False)
@click.argument('bar', required=False)
def cmd(foo, bar):
# ...
@click.command()
@click.argument('foobar', required=False)
def cmd(foobar):
# ...
解决方案
click.Command
您可以通过创建自定义类为每个命令处理程序添加不同数量的参数。如果不严格要求参数,则最好调用哪个命令处理程序存在一些歧义,但这主要可以通过使用适合传递的命令行的第一个签名来处理。
自定义类
class AlternateArgListCmd(click.Command):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.alternate_arglist_handlers = [(self, super())]
self.alternate_self = self
def alternate_arglist(self, *args, **kwargs):
from click.decorators import command as cmd_decorator
def decorator(f):
command = cmd_decorator(*args, **kwargs)(f)
self.alternate_arglist_handlers.append((command, command))
# verify we have no options defined and then copy options from base command
options = [o for o in command.params if isinstance(o, click.Option)]
if options:
raise click.ClickException(
f'Options not allowed on {type(self).__name__}: {[o.name for o in options]}')
command.params.extend(o for o in self.params if isinstance(o, click.Option))
return command
return decorator
def make_context(self, info_name, args, parent=None, **extra):
"""Attempt to build a context for each variant, use the first that succeeds"""
orig_args = list(args)
for handler, handler_super in self.alternate_arglist_handlers:
args[:] = list(orig_args)
self.alternate_self = handler
try:
return handler_super.make_context(info_name, args, parent, **extra)
except click.UsageError:
pass
except:
raise
# if all alternates fail, return the error message for the first command defined
args[:] = orig_args
return super().make_context(info_name, args, parent, **extra)
def invoke(self, ctx):
"""Use the callback for the appropriate variant"""
if self.alternate_self.callback is not None:
return ctx.invoke(self.alternate_self.callback, **ctx.params)
return super().invoke(ctx)
def format_usage(self, ctx, formatter):
"""Build a Usage for each variant"""
prefix = "Usage: "
for _, handler_super in self.alternate_arglist_handlers:
pieces = handler_super.collect_usage_pieces(ctx)
formatter.write_usage(ctx.command_path, " ".join(pieces), prefix=prefix)
prefix = " " * len(prefix)
使用自定义类:
要使用自定义类,请将其作为cls
参数传递给click.command
装饰器,例如:
@click.command(cls=AlternateArgListCmd)
@click.argument('foo')
@click.argument('bar')
def cli(foo, bar):
...
然后使用alternate_arglist()
命令上的装饰器添加另一个具有不同参数的命令处理程序。
@cli.alternate_arglist()
@click.argument('foobar')
def cli_one_param(foobar):
...
这是如何运作的?
这是因为 click 是一个设计良好的 OO 框架。@click.command()
装饰器通常实例化一个对象click.Command
,但允许使用cls
参数覆盖此行为。因此,从click.Command
我们自己的类中继承并覆盖所需的方法是一件相对容易的事情。
在这种情况下,我们添加一个新的装饰器方法:alternate_arglist()
,并覆盖三个方法:make_context(), invoke() & format_usage()
。被覆盖的make_context()
方法检查哪个命令处理程序变体与传递的参数数量匹配,被覆盖的invoke()
方法用于调用适当的命令处理程序变体,被覆盖format_usage()
的方法用于创建显示各种用法的帮助消息。
测试代码:
import click
@click.command(cls=AlternateArgListCmd)
@click.argument('foo')
@click.argument('bar')
@click.argument('baz')
@click.argument('bing', required=False)
@click.option('--an-option', default='empty')
def cli(foo, bar, baz, bing, an_option):
"""Best Command Ever!"""
if bing is not None:
click.echo(f'foo bar baz bing an-option: {foo} {bar} {baz} {bing} {an_option}')
else:
click.echo(f'foo bar baz an-option: {foo} {bar} {baz} {an_option}')
@cli.alternate_arglist()
@click.argument('foo')
@click.argument('bar')
def cli_two_param(foo, bar, an_option):
click.echo(f'foo bar an-option: {foo} {bar} {an_option}')
@cli.alternate_arglist()
@click.argument('foobar', required=False)
def cli_one_param(foobar, an_option):
click.echo(f'foobar an-option: {foobar} {an_option}')
if __name__ == "__main__":
commands = (
'',
'p1',
'p1 p2 --an-option=optional',
'p1 p2 p3',
'p1 p2 p3 p4 --an-option=optional',
'p1 p2 p3 p4 p5',
'--help',
)
import sys, time
time.sleep(1)
print('Click Version: {}'.format(click.__version__))
print('Python Version: {}'.format(sys.version))
for cmd in commands:
try:
time.sleep(0.1)
print('-----------')
print('> ' + cmd)
time.sleep(0.1)
cli(cmd.split())
except BaseException as exc:
if str(exc) != '0' and \
not isinstance(exc, (click.ClickException, SystemExit)):
raise
测试结果:
Click Version: 7.1.2
Python Version: 3.8.5 (tags/v3.8.5:580fbb0, Jul 20 2020, 15:57:54) [MSC v.1924 64 bit (AMD64)]
-----------
>
foobar an-option: None empty
-----------
> p1
foobar an-option: p1 empty
-----------
> p1 p2 --an-option=optional
foo bar an-option: p1 p2 optional
-----------
> p1 p2 p3
foo bar baz an-option: p1 p2 p3 empty
-----------
> p1 p2 p3 p4 --an-option=optional
foo bar baz bing an-option: p1 p2 p3 p4 optional
-----------
> p1 p2 p3 p4 p5
Usage: test_code.py [OPTIONS] FOO BAR BAZ [BING]
test_code.py [OPTIONS] FOO BAR
test_code.py [OPTIONS] [FOOBAR]
Try 'test_code.py --help' for help.
Error: Got unexpected extra argument (p5)
-----------
> --help
Usage: test_code.py [OPTIONS] FOO BAR BAZ [BING]
test_code.py [OPTIONS] FOO BAR
test_code.py [OPTIONS] [FOOBAR]
Best Command Ever!
Options:
--an-option TEXT
--help Show this message and exit.
推荐阅读
- boolean-logic - 我如何简化 f = x'yz + xy'z + xyz'?
- microsoft-graph-api - 使用 microsoft-graph 安排会议时的时区问题
- r - 创建自定义类的对象并为其分配方法
- flutter - Flutter 选项卡失去焦点或没有响应点击
- selenium-extent-report - 可以生成的范围报告的大小是否有任何限制?
- swift - 如何使用 uiprefetching 委托使用 AlamofireImage 预取图像?
- sql-server - 在 TO_DATE 函数中使用变量
- javascript - 等待地图函数中的所有承诺
- tfs - 如何在 Azure DevOps 中添加和签入代码?
- websphere - WebSphere Portal 主题:访问页面元数据