9

我对 asyncio 完全陌生ContextVars,我刚刚阅读了 3.7 中的新功能并发现ContextVars,我很难理解它的用法,我只知道它在协程中很有帮助,而不是使用thread.local应该使用ContextVars。但是官方文档和顶级谷歌搜索结果都不能帮助我真正理解它的目的。

那么凸变量是否跨模块共享?我试过了:

例子.py

from contextvars import ContextVar

number = ContextVar('number', default=100)
number.set(1)

然后我尝试导入 number.py

(playground) Jamess-MacBook-Pro-2:playground jlin$ python3.7
Python 3.7.0 (v3.7.0:1bf9cc5093, Jun 26 2018, 23:26:24) 
[Clang 6.0 (clang-600.0.57)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import example
>>> from contextvars import ContextVar
>>> number = ContextVar('number', default=200)
>>> number.get()
200

我原以为number.get()会返回 1,但显然我理解它的目的是错误的。

有人可以帮我理解这一点吗?

4

4 回答 4

8

假设您的用例是使用 pythonthread.local将您的线程全局变量存储在多线程应用程序中。

例如,您将 djangorequest全局存储在thread.local

  • 您的所有代码都可以访问当前(和正确)request实例
  • 它可以工作,因为每个 django HTTP 请求都在它自己的 python 线程中处理

无法想象您将异步中的 HTTP 请求作为在同一个 python 线程中执行的非阻塞代码来处理。

  • 存储在其中的 HTTP 请求thread.local不起作用,因为多个并发请求在同一个 python 线程中处理
  • 发生的情况是您覆盖了相同的thread.local变量并且您的所有代码都可以访问最新的(不正确的)request实例

在这种情况下,您使用ContextVars设计用于在同一 python 线程中运行的非阻塞并发作业中用于此用例。

于 2019-05-31T00:16:29.083 回答
2

您正在重新分配 的值number。直接从模块中调用变量,例如example.number.get().


一个简单的应用程序是替换一个全局变量。

给定

import random
import contextvars as cv

代码

在这里,我们将模拟随机游走。像全局变量一样,我们能够在函数之间共享状态:

move = cv.ContextVar("move", default="")


def go_left():
    value = move.get()
    move.set(value + "L")


def go_right():
    value = move.get()
    move.set(value + "R")


def random_walk(steps):
    directions = [go_left, go_right]
    while steps:
        random.choice(directions)()
        steps -= 1
    return move.get()

演示

ContextVar充当由随机事件更新的全局变量:

random_walk(1)
# 'R'
random_walk(2)
# 'RLL'
random_walk(3)
# 'RLLLRL'

除了常规的全局变量之外,ContextVar

于 2019-05-30T06:33:01.370 回答
0

您已再次为该号码分配值。这就是为什么它显示最新更新中的 200。

于 2019-05-30T05:06:51.273 回答
0

不完全确定我的方法是否正确,但我会添加我的发现来回答,所以我尝试在我的 django 项目中使用它来设置requestvar 而不使用本地线程。

from contextvars import ContextVar
request_var = ContextVar('request', default=None)


def request_middleware(get_response):
    """
    Sets the request into contextvar
    """
    def middleware(request):
        ctx = copy_context()
        return ctx.run(wrap_get_response, request)

    def wrap_get_response(request):
        request_var.set(request)
        return get_response(request)
    return middleware

然后在其他需要使用requestvar的地方:

from core.middlewares import request_var
request = request_var.get()

不确定这是否是正确的用法,但对我来说似乎工作正常。

我不知道如何从 context.get() 例如获取 request_var。

from contextvars import copy_context
ctx = copy_context()
# the key here is `request_var` obj in middlewares.py, 
# might as well just import `request_var` and use it directly?
request = ctx.get(...)  
于 2019-05-30T23:41:06.090 回答