跨模块的ContextVars

时间:2019-05-30 03:50:19

标签: python python-3.7 python-contextvars

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

那么converttvars是否在模块之间共享?我尝试过:

example.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 个答案:

答案 0 :(得分:1)

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

例如,您在request

中全局存储Django thread.local
  • 您所有的代码都可以访问当前(且正确)的request实例
  • 它有效,因为每个django HTTP请求都在其自己的python线程中处理

想象不到您将asyncio中的HTTP请求作为在同一python线程中执行的非阻塞代码来处理。

  • 存储在thread.local中的HTTP请求将无法正常工作,因为在同一个python线程中处理了多个并发请求
  • 发生的事情是您覆盖相同的thread.local变量,并且您的所有代码都可以访问最新的(错误的)request实例

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

答案 1 :(得分:0)

您已再次为数字指定值。这就是为什么它显示最新更新中的200。

答案 2 :(得分:0)

您正在重新分配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

答案 3 :(得分:0)

不能完全确定我的方法是否正确,但是我将添加调查结果以作答,因此我尝试在django项目中使用它来设置request var,而不使用本地线程。

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

然后将其放在需要使用request var的其他地方:

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(...)