Python 3.7 added the contextvars module (https://docs.python.org/ja/3/library/contextvars.html) and introduced the ContextVar class for asyncio.
Like Thread Local (in Python threading.local ()) which can have unique data for each thread In addition, the feature of ContextVar is that each coroutine can have unique data.
When I actually used ContextVar, I encountered a phenomenon that the data set in ContextVar disappeared, so I wrote a verification code again and investigated the behavior.
The code below executes two coroutine functions, parent_await_coroutine ()
and parent_create_new_task ()
, in which each function sets a value in ContextVar and calls the child ()
function.
This child ()
function modifies the value retrieved from ContextVar.
The two parent functions call the child function differently. The former awaits the coroutine and the latter creates and executes a new task that wraps the coroutine.
When run as the latter new Task, some of the changes to the ContextVar made by the child function are not reflected in the parent function. Specifically, the child function adds the value of number_var
ContextVar and resets it, but the parent function does not read the change (as it was before calling the child function).
On the other hand, changes to the msg_var
ContextVar to Msg objects are also visible in the parent function.
This is because the contents of contextvars were copied when the new task was created. You can read that from PEP 567.
In this copy process, if the int of number_var
is int, the value is copied, and the Msg object of msg_var
is referenced copied (that is, Shallow Copy), so it is considered that the above behavior is performed. ..
import asyncio
import contextvars
class Msg:
"""Just a text container class.
Used to check the Shallow copy of contextvars.
"""
def __init__(self, text: str):
self._text = text
@property
def text(self) -> str:
return self._text
@text.setter
def text(self, val):
self._text = val
msg_var: contextvars.ContextVar[Msg] = contextvars.ContextVar('msg_var')
number_var: contextvars.ContextVar[int] = contextvars.ContextVar('number_var')
async def child():
#Get a number from ContextVar and add 1
n = number_var.get()
print(f'child: number={n}') # child: number=1
n += 1
number_var.set(n)
#Get the Msg object from ContextVar and change the text
msg = msg_var.get()
print(f'child: msg="{msg.text}"') # child: msg="msg created by parent"
msg.text = 'msg changed by child'
#This child()To make an async function
await asyncio.sleep(0.1)
async def parent_await_coroutine():
n = 1
number_var.set(n)
m = Msg('msg created by parent')
msg_var.set(m)
print(f'parent: number={n}') # parent: number=1
print(f'parent: msg="{m.text}"') # parent: msg="msg created by parent"
await child()
n = number_var.get()
m = msg_var.get()
print(f'parent: number={n}') # parent: number=2
print(f'parent: msg="{m.text}"') # parent: msg="msg changed by child"
async def parent_create_new_task():
n = 1
number_var.set(n)
m = Msg('msg created by parent')
msg_var.set(m)
print(f'parent: number={n}') # parent: number=1
print(f'parent: msg="{m.text}"') # parent: msg="msg created by parent"
await asyncio.create_task(child())
n = number_var.get()
m = msg_var.get()
print(f'parent: number={n}') # parent: number=1
print(f'parent: msg="{m.text}"') # parent: msg="msg changed by child"
if __name__ == '__main__':
asyncio.run(parent_create_new_task())
asyncio.run(parent_await_coroutine())
Recommended Posts