You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python Click可变选项:如何在装饰器内获取上下文动态生成选项

问题描述

需要基于前序命令参数feature_name动态生成Python Click命令update的选项:根据feature_name从预设字典features中匹配对应选项列表,添加到update命令中。例如执行cli do feature1 update --option11时,需加载feature1对应的option11、option77选项。

当前硬编码feature_name为"feature1"时功能正常,但在variable_options装饰器内尝试通过click.get_current_context()或@click.pass_context获取上下文以动态获取feature_name时,会触发“上下文不存在”的运行时错误。要求不能使用allow_extra_args,因为需要在帮助信息中展示所有可用选项。

原始代码:

import click
import functools

features = {
    "feature1": {
        "option11",
        "option77"
    },
    "feature2": {
        "option33",
        "option44"
    },
}

def wrapper(f, opt):
    @click.option(f"--{opt}")
    @functools.wraps(f)
    def inner(*args, **kwargs):
        return f(*args, **kwargs)

    return inner


def variable_options(f):
    # ctx = click.get_current_context() <-- 此处无法获取上下文
    # arg = ctx.parent.params.get("feature_name") <-- 需要获取该参数
    arg = "feature1" # 硬编码值
    opts = features[arg]

    for opt in opts:
        f = wrapper(f, opt)
    return f

@click.group()
def cli():
    pass

@cli.group("do")
@click.argument("feature_name")
def do(feature_name):
    pass

@do.command("update")
@variable_options
@click.pass_context
def update(ctx, **kwargs):
    # 此处能获取到需要的feature_name,但装饰器内无法获取
    print(ctx.parent.params.get("feature_name"))
    print(kwargs)
    exit(0)
解决方案

问题根源在于:装饰器是程序加载阶段执行的,此时Click上下文尚未创建,因此无法通过click.get_current_context()获取参数。正确的做法是在命令解析阶段(此时上下文已初始化,父级参数已解析)动态添加选项。

可以通过自定义click.Command子类,重写parse_args方法实现动态选项添加,同时保证帮助信息能正确展示对应选项:

import click

features = {
    "feature1": {
        "option11",
        "option77"
    },
    "feature2": {
        "option33",
        "option44"
    },
}

class DynamicOptionCommand(click.Command):
    def __init__(self, name, callback, **kwargs):
        self.base_callback = callback
        super().__init__(name, **kwargs)

    def parse_args(self, ctx, args):
        # 从父上下文获取已解析的feature_name
        feature_name = ctx.parent.params.get("feature_name")
        if feature_name in features:
            # 动态添加对应选项(这里默认设为布尔型选项,可根据需求调整)
            for opt_name in features[feature_name]:
                option = click.Option(
                    [f"--{opt_name}"],
                    is_flag=True,
                    help=f"Option for {feature_name}"
                )
                self.params.append(option)
        # 调用父类方法完成参数解析
        super().parse_args(ctx, args)

    def invoke(self, ctx):
        # 调用原始命令回调函数
        return self.base_callback(ctx, **ctx.params)

@click.group()
def cli():
    pass

@cli.group("do")
@click.argument("feature_name")
def do(feature_name):
    pass

# 定义update命令的核心逻辑
def update(ctx, **kwargs):
    print(f"Selected feature: {ctx.parent.params.get('feature_name')}")
    print(f"Received options: {kwargs}")

# 使用自定义Command类注册update命令
do.add_command(DynamicOptionCommand("update", callback=update))

if __name__ == "__main__":
    cli()

效果验证

  • 执行cli do feature1 update --help,帮助信息会显示--option11和--option77选项
  • 执行cli do feature1 update --option11,会输出对应的feature名称和选项参数
  • 执行cli do feature2 update --option44,会加载feature2的选项并正常输出

内容的提问来源于stack exchange,提问作者David White

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 08:15:35