【问题标题】:Spooky action observed in Python context manager在 Python 上下文管理器中观察到的诡异动作
【发布时间】:2020-02-17 21:53:45
【问题描述】:

在 Python 3 中使用 contextmanager(我还没有测试过 Python 2)对于在 with 子句的范围内声明的变量有一些奇怪的行为。

在我看来,这些变量的行为类似于“远处的怪异动作”,因为只有在观察到它们时它们才似乎存在(来自非物理工程师的笑话)。

在上下文管理器范围内,在屈服点之后:

如果你……

  1. 打印出locals()

那么变量确实存在。

但如果你:

  1. 打印出locals()
  2. 对托管范围的变量做任何事情

那么变量就存在了!!

看这个例子:

from contextlib import contextmanager

def groucho():
    @contextmanager
    def groucho_manager(**kwargs):
        yield
        print("groucho_manager locals", locals())
        a

    with groucho_manager(lolcat=10):
        a = 50

def harpo():
    @contextmanager
    def harpo_manager(**kwargs):
        yield
        print("harpo_manager locals", locals())

    with harpo_manager(lolcat=10):
        b = 100

groucho()
harpo()

输出:

groucho_manager locals {'kwargs': {'lolcat': 10}, 'a': 50}
harpo_manager locals {'kwargs': {'lolcat': 10}}

可能与Python class inheritance - spooky action有关,但我不确定。

【问题讨论】:

  • 封闭函数中的变量只有在内部函数在编译时实际上引用了变量时才可用于内部函数。此类变量的实现比普通的局部变量要复杂得多(因为它们可以比直接包含它们的函数寿命更长);除非明确需要,否则 Python 不会做这些额外的工作。上下文管理器与此无关,它是嵌套函数定义的一般属性。
  • 或者换句话说:locals() 只返回函数实际引用的变量,不是它可能引用的每个外部变量。
  • @jasonharper 谢谢解答。他们是否在测试变量 "potentially existing in locals" 的存在性?我的意思是:getattr(locals(), 'var_name', default_value) 不起作用,因为在编译时没有检测到var_name 那么使用什么呢?我看到的唯一解决方案是 try: ... except:... 子句,但在这种情况下看起来很讨厌
  • 顺便说一句,我有一个 (可能是邪恶的,但在我看来是合法的) 这样做的原因:with log_request(args):\n response=send_request(args) 在这里,如果 log_request 可以维持存在可能会很好response,所以它可以同时记录 request_argsresponse 对象
  • 上下文管理器无法访问在其控制下运行的代码的局部变量。您只能在第一个示例中看到 a,因为它是嵌套在同一范围内的函数,而不是因为它是上下文管理器。

标签: python contextmanager


【解决方案1】:

我最初的用例是创建一个inner scope aware context manager 来处理日志记录。

我认为我可以滥用问题中列出的奇怪行为,但正如@jasonharper 指出的那样:它与嵌套范围有关,不能被滥用。

所以...我决定为这一切制定一个非常老套/滥用的解决方案:

scope_injected_contextmanager 在 pypi 上

这是自述文件中的简单示例:

from scope_injected_contextmanager import scope_injected_contextmanager

fetch = lambda request: ("ok", 200)

@scope_injected_contextmanager
def log_request(request, response):
    print(f"request: {request} response: {response}")

with log_request:
    request = {
        "query_args": {
            'foo': 10
        }
    }
    response = fetch(request)

# prints
# request: {'query_args': {'foo': 10}} response: ('ok', 200)

github查看更多信息

当心...它的:

  1. 真的很老套
  2. 完全没有经过充分测试
  3. 真的很丑很虐
  4. 大量使用检查(参见 1-3)

【讨论】:

    猜你喜欢
    • 2015-02-27
    • 2016-05-30
    • 1970-01-01
    • 1970-01-01
    • 2016-09-22
    • 1970-01-01
    • 1970-01-01
    • 2020-06-07
    • 1970-01-01
    相关资源
    最近更新 更多