首页 > 解决方案 > 单击 CLI 中缺少 1 个必需的位置参数

问题描述

我已经编写了一个带有 click 的 CLI,最初是作为一个模块,它工作得很好。但是由于我的项目变得更大,我现在需要具有 CLI 可以使用的属性,所以我试图将它变成一个类,但我遇到了一个错误。我的代码如下:

import click
import click_repl
import os
from prompt_toolkit.history import FileHistory

class CLI:
    def __init__(self):
        pass

    @click.group(invoke_without_command=True)
    @click.pass_context
    def cli(self, ctx):
        if ctx.invoked_subcommand is None:
            ctx.invoke(self.repl)

    @cli.command()
    def foo(self):
        print("foo")

    @cli.command()
    def repl(self):
        prompt_kwargs = {
            'history': FileHistory(os.path.expanduser('~/.repl_history'))
        }
        click_repl.repl(click.get_current_context(), prompt_kwargs)

    def main(self):
        while True:
            try:
                self.cli(obj={})
            except SystemExit:
                pass


if __name__ == "__main__":
    foo = CLI()
    foo.main()

没有所有的selfs 和class CLI:CLI 按预期工作,但作为一个类它会遇到错误:TypeError: cli() missing 1 required positional argument: 'ctx'我不明白为什么会发生这种情况。据我所知,调用self.cli()应该self自动传递,因此obj={}应该作为 ctx.obj 传递,所以它不应该cli对它是否包含在一个类中产生任何影响。

有人可以向我解释,为什么会发生这种情况,更重要的是,我该如何解决?

如果这里相关的是完整的错误堆栈跟踪:

Traceback (most recent call last):
    File "C:/Users/user/.PyCharmCE2018.2/config/scratches/exec.py", line 
     37, in <module>
      foo.main()
    File "C:/Users/user/.PyCharmCE2018.2/config/scratches/exec.py", line 
     30, in main
      self.cli(obj={})
    File "C:\Users\user\AppData\Local\Programs\Python\Python37\lib\site- packages\click\core.py", line 764, in __call__
      return self.main(*args, **kwargs)
    File "C:\Users\user\AppData\Local\Programs\Python\Python37\lib\site-packages\click\core.py", line 717, in main
      rv = self.invoke(ctx)
    File "C:\Users\user\AppData\Local\Programs\Python\Python37\lib\site-packages\click\core.py", line 1114, in invoke
      return Command.invoke(self, ctx)
    File "C:\Users\user\AppData\Local\Programs\Python\Python37\lib\site-packages\click\core.py", line 956, in invoke
      return ctx.invoke(self.callback, **ctx.params)
    File "C:\Users\user\AppData\Local\Programs\Python\Python37\lib\site-packages\click\core.py", line 555, in invoke
      return callback(*args, **kwargs)
    File "C:\Users\user\AppData\Local\Programs\Python\Python37\lib\site-packages\click\decorators.py", line 17, in new_func
      return f(get_current_context(), *args, **kwargs)
TypeError: cli() missing 1 required positional argument: 'ctx'

编辑:问题似乎是pass_context电话。通常pass_context会将当前上下文作为函数的第一个参数提供,以便obj={}将其传递给上下文实例。但由于我将点击组包装到一个类中,第一个位置由self-reference 占据,因此当前上下文无法传递给函数。关于如何解决这个问题的任何想法?

我尝试更改def cli()以下方式:

@click.group(invoke_without_command=True)
def cli(self):
    ctx = click.get_current_context()
    ctx.obj = {}
    if ctx.invoked_subcommand is None:
        ctx.invoke(self.repl)

所以我不会通过调用来传递上下文,以避免与 冲突self,但是如果我尝试运行它,则会发生self.cli()错误TypeError: cli() missing 1 required positional argument: 'self'。调用它时self.cli(self)遇到TypeError: 'CLI' object is not iterable

标签: pythonpython-3.x

解决方案


恐怕 click 库不是为类而设计的。Click 使用了装饰器。不要太轻视装饰器。装饰器实际上将您的函数作为参数并返回不同的函数。

例如:

@cli.command()
def foo(self):

是否符合

foo = cli.command()(foo)

所以,恐怕click已经不支持修饰类绑定的函数了,只能修饰未绑定的函数。所以,基本上你的答案的解决方案是,不要使用类。

您现在可能想知道如何组织您的代码。大多数语言将班级作为一个组织单位呈现给您。

然而,Python 更进了一步,也为您提供了模块。基本上,一个文件就是一个模块,在这个文件中,你放入的所有内容都会作为一个模块自动与该文件相关联。

因此,只需命名一个文件cli.py并将您的属性创建为全局变量。这可能会给您带来其他问题,因为您无法在函数范围内更改全局变量,但您可以使用类来包含您的变量。

class Variables:
    pass

variables = Variables()
variables.something = "Something"

def f():
    variables.something = "Nothing"

推荐阅读