Python asyncio and ContextVar

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

Python asyncio and ContextVar
[python] Compress and decompress
Python and numpy tips
[Python] pip and wheel
Batch design and python
Python iterators and generators
Python packages and modules
Vue-Cli and Python integration
Ruby, Python and map
python input and output
Python and Ruby split
Python3, venv and Ansible
Programming with Python and Tkinter
Encryption and decryption with Python
Python: Class and instance variables
3-3, Python strings and character codes
Python and hardware-Using RS232C with Python-
Python on Ruby and angry Ruby on Python
Python indentation and string format
Python real division (/) and integer division (//)
Install Python and Flask (Windows 10)
About python objects and classes
About Python variables and objects
Apache mod_auth_tkt and Python AuthTkt
Å (Ongustromu) and NFC @ Python
Understand Python packages and modules
# 2 [python3] Separation and comment out
Python shallow copy and deep copy
Python and ruby slice memo
Python installation and basic grammar
I compared Java and Python!
Python shallow and deep copy
About Python, len () and randint ()
Install Python 3.7 and Django 3.0 (CentOS)
Python class variables and instance variables
Ruby and Python syntax ~ branch ~
[Python] Python and security-① What is Python?
Stack and Queue in Python
python metaclass and sqlalchemy declareative
Fibonacci and prime implementations (python)
Python basics: conditions and iterations
Python bitwise operator and OR
Python debug and test module
Python list and tuples and commas
Python variables and object IDs
Python list comprehensions and generators
About Python and regular expressions
python with pyenv and venv
Unittest and CI in Python
[python] Get quotient and remainder
Python 3 sorted and comparison functions
[Python] Depth-first search and breadth-first search
Identity and equivalence Python is and ==
Source installation and installation of Python
Python or and and operator trap
Challenge Python3 and Selenium Webdriver
About Python and os operations
Python higher-order functions and comprehensions
Python (Python 3.7.7) installation and basic grammar
Python # About reference and copy
Works with Python and R