Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 224
James Lin Avatar asked May 30 '19 03:05

James Lin


2 Answers

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.

like image 150
mojeto Avatar answered Oct 15 '22 07:10

mojeto


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:

  • permits default values
  • works with context managers
  • works with coroutines
like image 24
pylang Avatar answered Oct 15 '22 09:10

pylang