James Lin
James Lin

Reputation: 26538

ContextVars across modules

I am completely newb on asyncio and ContextVars, I just read up what's new in 3.7 and discovered ContextVars, I struggle to understand it's usage, all I know it's helpful in coroutines, instead of using thread.local should use ContextVars. But none of the official doc and top google search result could help me truely understand its purpose.

So is convextvars shared across modules? I tried:

example.py

from contextvars import ContextVar

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

then I try to import 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

I was expecting number.get() would return 1, but obviously I have understood its purpose wrong.

Could someone please help me understand this?

Upvotes: 17

Views: 8469

Answers (4)

mojeto
mojeto

Reputation: 586

Assume your use case is to use python thread.local to store your thread global variable inside multi thread application.

For example you store django request globally in thread.local

  • all your code has access to current (and correct) request instance
  • it works, because each django HTTP request handled in it's own python thread

No imagine you handle HTTP requests in asyncio as non blocking code executed in the same python thread.

  • HTTP request stored in thread.local isn't going to work, because multiple concurrent requests get processed in the same python thread
  • What happens is you overwrite same thread.local variable and all your code has access to latest (incorrect) request instance

In this case you use ContextVars designed to be used for this use case inside non blocking concurrent jobs running in same python thread.

Upvotes: 10

James Lin
James Lin

Reputation: 26538

Not completely sure if my approach is correct but I will add my findings to answer, so I tried to use this in my django project to set the request var without using thread local.

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

Then in somewhere else that need to use the request var:

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

Not sure if this is the correct usage, but it seems working fine for me.

I don't know how to get the request_var from context.get() eg.

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

Upvotes: -1

pylang
pylang

Reputation: 44485

You are reassigning the value of number. Directly call the variable from the module, e.g. example.number.get().


A simple application is substituting a global variable.

Given

import random
import contextvars as cv

Code

Here we'll imitate a random walk. Like a global variable, we are able to share state between functions:

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

Demo

The ContextVar acts as a global variable that is updated by random events:

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

Beyond a regular global variable, ContextVar:

Upvotes: 3

ASHISH ARORA
ASHISH ARORA

Reputation: 9

You have assigned the value for the number again. Which is why it shows 200 from the latest update.

Upvotes: 0

Related Questions