如何自省Click应用?DC应用命令信息提取方法问询
我有一个基于click.Group构建的名为DC的Click应用cli(),包含大量子命令。我想生成一份文本文件,列出所有命令、参数、选项及帮助文本作为便捷参考,因此需要了解如何自省这个Click应用。
我查阅了Click的API参考文档但感到困惑。Command的部分方法(如get_usage())需要传入context对象作为首个参数,我只知道两种获取context的方式:
- 在使用
@pass_context装饰器的命令内部获取(并非总能实现) - 调用
click.get_current_context()获取当前正在执行的底层命令对应的context
我尝试了以下代码:
from dc.__main__ import cli current_ctx = click.get_current_context() click.echo(cli.get_usage(current_ctx))
这段代码能打印出cli()函数的文档字符串,但执行click.echo(cli.commands)时得到的是空字典。后来我通过current_ctx.find_root().command.commands获取到了预期的所有顶层命令字典,想知道这是不是首选方法。
解答
关于获取顶层命令的方法
你用current_ctx.find_root().command.commands获取顶层命令的方法是可行的,但还有更直接的优化方向:
当你从模块导入cli(即click.Group实例)后,如果子命令是通过@cli.command()或cli.add_command()注册的,正常情况下cli.commands应该包含所有子命令。如果它是空字典,大概率是因为你的子命令采用了延迟加载(比如用了懒导入、动态注册逻辑),只有上下文初始化后子命令才会被加载,所以current_ctx.find_root().command.commands才能拿到完整列表。
更可靠的自省方案
如果要生成完整的命令参考,建议手动构建上下文,而非依赖click.get_current_context()(这个方法仅在命令执行过程中有效,直接运行脚本调用会报错)。可以手动实例化click.Context:
from dc.__main__ import cli import click # 手动创建根上下文 ctx = click.Context(cli) # 触发参数解析,加载所有延迟注册的子命令 ctx.command.parse_args(ctx, []) # 遍历所有顶层命令 for cmd_name, cmd in ctx.command.commands.items(): print(f"命令: {cmd_name}") print(f"帮助文本: {cmd.help or '无说明'}") print(f"用法: {cmd.get_usage(ctx)}") # 打印选项 print("选项:") for param in cmd.params: if isinstance(param, click.Option): print(f" - {', '.join(param.opts)}: {param.help or '无说明'}") # 打印参数 print("参数:") for param in cmd.params: if isinstance(param, click.Argument): print(f" {param.name}: {param.help or '无说明'}") print("---")
递归遍历嵌套子命令
如果你的命令存在嵌套组(比如cli下有子组,子组下还有命令),可以用递归函数遍历所有层级:
def walk_commands(cmd, ctx, prefix=""): full_cmd = f"{prefix} {cmd.name}".strip() print(f"命令: {full_cmd}") print(f"帮助文本: {cmd.help or '无说明'}") print(f"用法: {cmd.get_usage(ctx)}") # 输出选项 print("选项:") for param in cmd.params: if isinstance(param, click.Option): print(f" - {', '.join(param.opts)}: {param.help or '无说明'}") # 输出参数 print("参数:") for param in cmd.params: if isinstance(param, click.Argument): print(f" {param.name}: {param.help or '无说明'}") print("---") # 如果是命令组,递归遍历子命令 if isinstance(cmd, click.Group): for sub_cmd_name, sub_cmd in cmd.commands.items(): walk_commands(sub_cmd, ctx, prefix=full_cmd) # 初始化上下文并启动遍历 ctx = click.Context(cli) ctx.command.parse_args(ctx, []) walk_commands(cli, ctx)
这个方案无需依赖运行中的命令上下文,可直接在脚本中执行,能完整获取所有命令、参数、选项和帮助文本,适合生成静态参考文档。
内容的提问来源于stack exchange,提问作者odigity

