为什么 python-click 不在子命令之间传递上下文?

Why doesn't python-click pass context between subcommands?

我正在尝试通过 python-click 在两个子命令之间传递上下文。这是一个 MWE:

import click


@click.group(chain=True)
def cli() -> None:
    pass


@cli.command()
@click.pass_context
def fn1(cxt):
    cxt.obj = 1


@cli.command()
@click.pass_context
def fn2(cxt):
    print(f'{cxt.obj=}')


if __name__ == '__main__':
    cli()

如果我用 cli.py fn1 fn2 调用它,我希望得到“cxt.obj=1”,而我得到“cxt.obj=None”。

在尝试调试时,我还注意到我可以从 fn1fn2 访问 cli 的上下文,但不能从 fn2。因此,我可以在 fn1 中设置 cli 上下文的对象,并在 fn2 中读取 cli 的上下文,但必须有更好的方法,其中 obj可在 fn2 的上下文中直接访问。

我的心智模型有什么问题,为什么子命令之间的上下文不持久?而且,当需要在子命令之间传递数据时,最好使用什么模式?

上下文从父项复制到子项。在你的例子中,context.obj for cli() 你没有在你的例子中公开,是 NoneNone 被复制到 fn1()fn2() 的子上下文中,但随后在 fn1() 中,您将副本更改为 1。最后,在 fn2() 中,您会收到 cli() 上下文的副本,其中 obj 仍然是 None

因此,为了达到您想要的结果,有(至少)两个选择。

  1. 在父上下文中使 context.obj 成为一个可变对象,然后改变该对象
  2. 如您所说,可以通过 ctx.parent.obj = 1
  3. 之类的方式直接访问父上下文

第一种(我的首选)方法示例:

例子

import click, sys


@click.group(chain=True)
@click.pass_context
def cli(ctx) -> None:
    ctx.obj = {}


@cli.command()
@click.pass_context
def fn1(ctx):
    ctx.obj['fn1'] = 1


@cli.command()
@click.pass_context
def fn2(ctx):
    click.echo(f'obj: {ctx.obj}')

测试代码

if __name__ == "__main__":
    print('Click Version: {}'.format(click.__version__))
    print('Python Version: {}'.format(sys.version))
    print('-----------')
    cmd = 'fn1 fn2'
    print('> ' + cmd)
    cli(cmd.split())

结果:

Click Version: 8.1.3
Python Version: 3.7.8 (tags/v3.7.8:4b47a5b6ba, Jun 28 2020, 08:53:46) [MSC v.1916 64 bit (AMD64)]
-----------
> fn1 fn2
obj: {'fn1': 1}