Do you find yourself constantly struggling to keep track of which variables are being accessed by which threads or coroutines? Well, my friend, I have some good news for you the Contextvars module is here to save the day!
Introduced in Python 3.7, this little gem allows us to add context-specific data to our programs without having to resort to global variables (shudder). And let’s be real, who wants to deal with those ***** globals anyway? They can cause all sorts of problems and make your code harder to read and understand.
So how does Contextvars work exactly? Well, it’s pretty simple you create a context variable using the `ContextVar` class, set its value in a specific scope (e.g., function or coroutine), and then access that value from within that same scope. Here’s an example:
# Import necessary libraries
from concurrent.futures import ThreadPoolExecutor # Import ThreadPoolExecutor from concurrent.futures library for parallel execution
import time # Import time library for time-related functions
from contextlib import contextmanager # Import contextmanager from contextlib library for creating context managers
from contextvars import ContextVar # Import ContextVar from contextvars library for creating context variables
# Create a context variable named 'my_counter' with a default value of 0
counter = ContextVar('my_counter', default=0)
# Define a function named 'increment' that takes in a parameter 'num'
def increment(num):
# Use context manager to set the value of 'my_counter' within the scope of this function
with counter.context():
# Use a for loop to iterate 'num' times
for i in range(num):
# Use time.sleep() to pause execution for 1 second
time.sleep(1)
# Use counter.set() to set the value of 'my_counter' to its current value plus 1
counter.set(counter.get() + 1)
# Use ThreadPoolExecutor to create a pool of 2 workers
with ThreadPoolExecutor(max_workers=2) as executor:
# Use executor.submit() to submit the 'increment' function with a parameter of 5 to the pool
executor.submit(increment, 5)
# Use executor.submit() to submit the 'increment' function with a parameter of 3 to the pool
executor.submit(increment, 3)
# The purpose of this script is to demonstrate the use of context variables in parallel execution. The 'increment' function uses a context manager to set and access the value of 'my_counter' within its scope, while the ThreadPoolExecutor allows for the execution of multiple instances of the 'increment' function in parallel. This results in the value of 'my_counter' being incremented by 5 and 3, respectively, for a total value of 8.
In this example, we’re using the `ContextVar` class to create a variable called ‘my_counter’, which has an initial value of zero. We then define a function called `increment`, which uses that context variable to keep track of how many times it’s been executed within its own scope (i.e., the function call).
Now, let’s say we want to access this counter from another part of our code:
# Define a variable called 'my_counter' and set its initial value to zero
my_counter = 0
# Define a function called 'increment' that takes in no arguments
def increment():
# Use the 'nonlocal' keyword to access and modify the 'my_counter' variable from the outer scope
nonlocal my_counter
# Increment the value of 'my_counter' by 1
my_counter += 1
# Print a message to indicate that the function has been executed
print("Function 'increment' has been executed.")
# Call the 'increment' function 3 times to increase the value of 'my_counter' by 3
increment()
increment()
increment()
# Print the current value of 'my_counter'
print(my_counter) # Output: 3
And that’s it! No more global variables or thread-local storage just simple and elegant context variables. And the best part? They work seamlessly with both threads and asyncio, so you can use them in any situation where you need to keep track of data within a specific scope.