为了账号安全,请及时绑定邮箱和手机立即绑定

Python Click:全局访问选项值

Python Click:全局访问选项值

慕慕森 2023-11-09 21:55:46
假设我--debug/--no-debug为基本命令定义了一个标志。该标志将影响我的程序中许多操作的行为。现在我发现自己将此标志作为函数参数传递到各处,这看起来并不优雅。特别是当我需要在深层调用堆栈中访问此标志时,我必须将此参数添加到堆栈上的每个函数中。我可以创建一个全局变量is_debug,并在接收该标志值的命令函数的开头设置其值。但这对我来说似乎也不优雅。是否有更好的方法可以使用 Click 库使某些选项值全局可访问?
查看完整描述

2 回答

?
慕村9548890

TA贡献1884条经验 获得超4个赞

有两种方法可以实现此目的,具体取决于您的需要。他们最终都使用了 click Context。

就我个人而言,我很喜欢选项 2,因为这样我就不必修改函数签名(而且我很少编写多线程程序)。它听起来也更像你正在寻找的东西。

选项 1:将上下文传递给函数

使用click.pass_context装饰器将单击上下文传递给函数。

文件:

# test1.py

import click



@click.pass_context

def some_func(ctx, bar):

    foo = ctx.params["foo"]

    print(f"The value of foo is: {foo}")



@click.command()

@click.option("--foo")

@click.option("--bar")

def main(foo, bar):

    some_func(bar)



if __name__ == "__main__":

    main()

$ python test1.py --foo 1 --bar "bbb"

The value of foo is: 1

选项2:click.get_current_context()

通过 直接从当前线程中提取上下文click.get_current_context()。从 Click 5.0 开始可用。

文件:

注意:这仅在您位于当前线程(与最初用于设置单击命令的线程相同的线程)中时才有效。

# test2.py

import click



def some_func(bar):

    c = click.get_current_context()

    foo = c.params["foo"]

    print(f"The value of foo is: {foo}")



@click.command()

@click.option("--foo")

@click.option("--bar")

def main(foo, bar):

    some_func(bar)



if __name__ == "__main__":

    main()

$ python test2.py --foo 1 --bar "bbb"

The value of foo is: 1


查看完整回答
反对 回复 2023-11-09
?
Qyouu

TA贡献1786条经验 获得超11个赞

我想让它更加无缝,所以我将它与修改函数的全局范围的技巧结合起来,并提出了以下装饰器:

def with_click_params(func):

    @functools.wraps(func)

    def wrapper(*args, **kwargs):

        g = func.__globals__

        sentinel = object()


        ctx = click.get_current_context()

        oldvalues = {}

        for param in ctx.params:

            oldvalues[param] = g.get(param, sentinel)

            g[param] = ctx.params[param]


        try:

            return func(*args, **kwargs)

        finally:

            for param in ctx.params:

                if oldvalues[param] is sentinel:

                    del g[param]

                else:

                    g[param] = oldvalues[param]


    return wrapper

你可以像这样使用它(从@dthor的答案借用示例):


@with_click_params

def some_func():

    print(f"The value of foo is: {foo}")

    print(f"The value of bar is: {bar}")



@click.command()

@click.option("--foo")

@click.option("--bar")

def main(foo, bar):

    some_func()



if __name__ == "__main__":

    main()

这是它的实际效果:


$ python test2.py --foo 1 --bar "bbb"

The value of foo is: 1

The value of bar is: bbb

注意事项

  • 函数只能从click原始调用堆栈调用,但这是一个有意识的选择(即,您将对变量注入做出假设)。点击单元测试指南在这里应该很有用。

  • 该函数不再是线程安全的。

也可以明确要注入的参数名称:

def with_click_params(*params):

    def wrapper(func):

        @functools.wraps(func)

        def inner_wrapper(*args, **kwargs):

            g = func.__globals__

            sentinel = object()


            ctx = click.get_current_context()

            oldvalues = {}

            for param in params:

                oldvalues[param] = g.get(param, sentinel)

                g[param] = ctx.params[param]


            try:

                return func(*args, **kwargs)

            finally:

                for param in params:

                    if oldvalues[param] is sentinel:

                        del g[param]

                    else:

                        g[param] = oldvalue


        return inner_wrapper

    return wrapper



@with_click_params("foo")

def some_func():

    print(f"The value of foo is: {foo}")



@click.command()

@click.option("--foo")

@click.option("--bar")

def main(foo, bar):

    some_func()



if __name__ == "__main__":

    main()


查看完整回答
反对 回复 2023-11-09
  • 2 回答
  • 0 关注
  • 134 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信