【问题标题】:Use Python click command to invoke a class method with variadic arguments使用 Python click 命令调用具有可变参数的类方法
【发布时间】:2019-07-09 04:00:53
【问题描述】:

我有一个使用以前未知数量的参数初始化的类,我希望使用 Python 的 click 包在 CLI 上完成它。我的问题是我无法初始化它并运行click 命令:

$ python mycode.py arg1 arg2 ... argN click_command

设置定义数量的参数,如nargs=5,解决了缺少命令的问题,但要求我在命令前输入 5 个参数。对于 nargs=-1 这样的可变参数,click 不会将 click_command 识别为命令。

如何输入 n 个参数,然后使用 click 运行命令?

import click

class Foo(object):
    def __init__(self, *args):
        self.args = args

    def log(self):
        print('self.args:', self.args)

pass_foo = click.make_pass_decorator(Foo)

@click.group()
@click.argument('myargs', nargs=-1)
@click.pass_context
def main(ctx, myargs):
    ctx.obj = Foo(myargs)
    print("arguments: ", myargs)

@main.command()
@pass_foo
def log(foo):
    foo.log()

main()

我希望在将 n-many args 传递给我的 Foo() 类后能够运行 click 命令,因此我可以对其进行初始化并将其 log() 方法作为 CLI 命令运行,但输出是:

错误:缺少命令

【问题讨论】:

    标签: python command-line-interface args variadic python-click


    【解决方案1】:

    我不完全确定您尝试做的是解决此问题的最佳方法。我认为在命令之后放置可变参数会更合乎逻辑,并且肯定会更符合 click 的工作方式。但是,你可以用这个做你想做的事:

    自定义类:

    class CommandAfterArgs(click.Group):
    
        def parse_args(self, ctx, args):
            parsed_args = super(CommandAfterArgs, self).parse_args(ctx, args)
            possible_command = ctx.params['myargs'][-1]
            if possible_command in self.commands:
                ctx.protected_args = [possible_command]
                ctx.params['myargs'] = ctx.params['myargs'][:-1]
    
            elif possible_command in ('-h', '--help'):
                if len(ctx.params['myargs']) > 1 and \
                        ctx.params['myargs'][-2] in self.commands:
                    ctx.protected_args = [ctx.params['myargs'][-2]]
                    parsed_args = ['--help']
                    ctx.params['myargs'] = ctx.params['myargs'][:-2]
                    ctx.args = [possible_command]
    
            return parsed_args
    

    使用自定义类:

    然后要使用自定义类,请将其作为 cls 参数传递给组装饰器,例如:

    @click.group(cls=CommandAfterArgs)
    @click.argument('myargs', nargs=-1)
    def main(myargs):
        ...
    

    测试代码:

    import click
    
    class Foo(object):
        def __init__(self, *args):
            self.args = args
    
        def log(self):
            print('self.args:', self.args)
    
    
    pass_foo = click.make_pass_decorator(Foo)
    
    
    @click.group(cls=CommandAfterArgs)
    @click.argument('myargs', nargs=-1)
    @click.pass_context
    def main(ctx, myargs):
        ctx.obj = Foo(*myargs)
        print("arguments: ", myargs)
    
    
    @main.command()
    @pass_foo
    def log(foo):
        foo.log()
    
    
    if __name__ == "__main__":
        commands = (
            'arg1 arg2 log',
            'log --help',
            '--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)
                main(cmd.split())
    
            except BaseException as exc:
                if str(exc) != '0' and \
                        not isinstance(exc, (click.ClickException, SystemExit)):
                    raise
    

    结果:

    Click Version: 6.7
    Python Version: 3.6.3 (v3.6.3:2c5fed8, Oct  3 2017, 18:11:49) [MSC v.1900 64 bit (AMD64)]
    -----------
    > arg1 arg2 log
    arguments:  ('arg1', 'arg2')
    self.args: ('arg1', 'arg2')
    -----------
    > log --help
    arguments:  ()
    Usage: test.py log [OPTIONS]
    
    Options:
      --help  Show this message and exit.
    -----------
    > --help
    Usage: test.py [OPTIONS] [MYARGS]... COMMAND [ARGS]...
    
    Options:
      --help  Show this message and exit.
    
    Commands:
      log
    

    【讨论】:

    • +100 建议将命令放在参数之前,就像所有其他理智的命令一样。
    • 感谢您非常好的回答,@stephen。如果我将可变参数放在子命令之后,按照您的建议与 click 的工作方式保持一致,我将无法在 main() 组命令上初始化 Foo() 类,以便创建 Foo() 的实例由其子命令使用。我想出如何在这个 Foo(*args) 场景中运行子命令后跟 args 的唯一方法是在每个子命令中实例化它。那么这是正确的方法吗?尝试在 Foo(*args) 之类的类上单击是没用的吗?
    • 我相信您的用例可以随心所欲地处理。建议生成另一个问题,将其作为您尝试通过单击解决的特定问题。我也隐约记得在 SO 上已经回答过类似的问题。 maybe here
    • @StephenRauch 你确实已经完成了similar answer,我可以自己回答my question。谢谢。
    猜你喜欢
    • 2015-12-23
    • 1970-01-01
    • 1970-01-01
    • 2013-01-29
    • 1970-01-01
    • 2015-10-01
    • 2011-03-09
    • 2012-03-24
    • 2018-05-16
    相关资源
    最近更新 更多