了解 Python 上下文变量
Understanding Python contextvars
关于以下。我做了一些更改,以了解使用和不使用 Contextvars 之间的区别。
我预计在某些时候变量 myid
会损坏,但将范围更改为更高的数字似乎根本不会影响。
import asyncio
import contextvars
# declare context var
request_id = contextvars.ContextVar('Id of request.')
async def some_inner_coroutine(myid):
# get value
print('Processed inner coroutine of myid : {}'.format(myid))
print('Processed inner coroutine of request: {}'.format(request_id.get()))
if myid != request_id.get():
print("ERROR")
async def some_outer_coroutine(req_id):
# set value
request_id.set(req_id)
await some_inner_coroutine(req_id)
# get value
print('Processed outer coroutine of request: {}'.format(request_id.get()))
async def main():
tasks = []
for req_id in range(1, 1250):
tasks.append(asyncio.create_task(some_outer_coroutine(req_id)))
await asyncio.gather(*tasks)
if __name__ == '__main__':
asyncio.run(main())
结果
Processed inner coroutine of myid : 1
Processed inner coroutine of request: 1
Processed outer coroutine of request: 1
Processed inner coroutine of myid : 2
Processed inner coroutine of request: 2
Processed outer coroutine of request: 2
Processed inner coroutine of myid : 3
Processed inner coroutine of request: 3
Processed outer coroutine of request: 3
Processed inner coroutine of myid : 4
Processed inner coroutine of request: 4
Processed outer coroutine of request: 4
...
...
Processed inner coroutine of myid : 1244
Processed inner coroutine of request: 1244
Processed outer coroutine of request: 1244
Processed inner coroutine of myid : 1245
Processed inner coroutine of request: 1245
Processed outer coroutine of request: 1245
Processed inner coroutine of myid : 1246
Processed inner coroutine of request: 1246
Processed outer coroutine of request: 1246
Processed inner coroutine of myid : 1247
Processed inner coroutine of request: 1247
Processed outer coroutine of request: 1247
Processed inner coroutine of myid : 1248
Processed inner coroutine of request: 1248
Processed outer coroutine of request: 1248
Processed inner coroutine of myid : 1249
Processed inner coroutine of request: 1249
Processed outer coroutine of request: 1249
我应该更改什么才能看到变量 myid
的意外行为?
上下文变量 当您需要沿调用链传递变量以便它们共享相同的上下文时,在无法通过全局变量完成的情况下,这很方便并发 情况下的变量。 上下文变量 可以在多线程代码和异步(协同程序)中用作全局变量的替代方法。
asyncio
原生支持上下文变量,无需任何额外配置即可使用。因为当创建 Task
时,它会复制当前上下文,然后在复制的上下文中运行其协程:
# asyncio/task.py
class Task:
def __init__(self, coro):
...
# Get the current context snapshot.
self._context = contextvars.copy_context()
self._loop.call_soon(self._step, context=self._context)
def _step(self, exc=None):
...
# Every advance of the wrapped coroutine is done in
# the task's context.
self._loop.call_soon(self._step, context=self._context)
...
下面是您的示例,显示了与上下文变量相比全局变量的损坏:
import asyncio
import contextvars
# declare context var
current_request_id_ctx = contextvars.ContextVar('')
current_request_id_global = ''
async def some_inner_coroutine():
global current_request_id_global
# simulate some async work
await asyncio.sleep(0.1)
# get value
print('Processed inner coroutine of request: {}'.format(current_request_id_ctx.get()))
if current_request_id_global != current_request_id_ctx.get():
print(f"ERROR! global var={current_request_id_global}")
async def some_outer_coroutine(req_id):
global current_request_id_global
# set value
current_request_id_ctx.set(req_id)
current_request_id_global = req_id
await some_inner_coroutine()
# get value
print('Processed outer coroutine of request: {}\n'.format(current_request_id_ctx.get()))
async def main():
tasks = []
for req_id in range(1, 10000):
tasks.append(asyncio.create_task(some_outer_coroutine(req_id)))
await asyncio.gather(*tasks)
if __name__ == '__main__':
asyncio.run(main())
输出:
...
Processed inner coroutine of request: 458
ERROR! global var=9999
Processed outer coroutine of request: 458
Processed inner coroutine of request: 459
ERROR! global var=9999
Processed outer coroutine of request: 459
...
可以在 PЕP 567
中找到使用 threading.local()
转换代码的示例
关于以下
我预计在某些时候变量 myid
会损坏,但将范围更改为更高的数字似乎根本不会影响。
import asyncio
import contextvars
# declare context var
request_id = contextvars.ContextVar('Id of request.')
async def some_inner_coroutine(myid):
# get value
print('Processed inner coroutine of myid : {}'.format(myid))
print('Processed inner coroutine of request: {}'.format(request_id.get()))
if myid != request_id.get():
print("ERROR")
async def some_outer_coroutine(req_id):
# set value
request_id.set(req_id)
await some_inner_coroutine(req_id)
# get value
print('Processed outer coroutine of request: {}'.format(request_id.get()))
async def main():
tasks = []
for req_id in range(1, 1250):
tasks.append(asyncio.create_task(some_outer_coroutine(req_id)))
await asyncio.gather(*tasks)
if __name__ == '__main__':
asyncio.run(main())
结果
Processed inner coroutine of myid : 1
Processed inner coroutine of request: 1
Processed outer coroutine of request: 1
Processed inner coroutine of myid : 2
Processed inner coroutine of request: 2
Processed outer coroutine of request: 2
Processed inner coroutine of myid : 3
Processed inner coroutine of request: 3
Processed outer coroutine of request: 3
Processed inner coroutine of myid : 4
Processed inner coroutine of request: 4
Processed outer coroutine of request: 4
...
...
Processed inner coroutine of myid : 1244
Processed inner coroutine of request: 1244
Processed outer coroutine of request: 1244
Processed inner coroutine of myid : 1245
Processed inner coroutine of request: 1245
Processed outer coroutine of request: 1245
Processed inner coroutine of myid : 1246
Processed inner coroutine of request: 1246
Processed outer coroutine of request: 1246
Processed inner coroutine of myid : 1247
Processed inner coroutine of request: 1247
Processed outer coroutine of request: 1247
Processed inner coroutine of myid : 1248
Processed inner coroutine of request: 1248
Processed outer coroutine of request: 1248
Processed inner coroutine of myid : 1249
Processed inner coroutine of request: 1249
Processed outer coroutine of request: 1249
我应该更改什么才能看到变量 myid
的意外行为?
上下文变量 当您需要沿调用链传递变量以便它们共享相同的上下文时,在无法通过全局变量完成的情况下,这很方便并发 情况下的变量。 上下文变量 可以在多线程代码和异步(协同程序)中用作全局变量的替代方法。
asyncio
原生支持上下文变量,无需任何额外配置即可使用。因为当创建 Task
时,它会复制当前上下文,然后在复制的上下文中运行其协程:
# asyncio/task.py
class Task:
def __init__(self, coro):
...
# Get the current context snapshot.
self._context = contextvars.copy_context()
self._loop.call_soon(self._step, context=self._context)
def _step(self, exc=None):
...
# Every advance of the wrapped coroutine is done in
# the task's context.
self._loop.call_soon(self._step, context=self._context)
...
下面是您的示例,显示了与上下文变量相比全局变量的损坏:
import asyncio
import contextvars
# declare context var
current_request_id_ctx = contextvars.ContextVar('')
current_request_id_global = ''
async def some_inner_coroutine():
global current_request_id_global
# simulate some async work
await asyncio.sleep(0.1)
# get value
print('Processed inner coroutine of request: {}'.format(current_request_id_ctx.get()))
if current_request_id_global != current_request_id_ctx.get():
print(f"ERROR! global var={current_request_id_global}")
async def some_outer_coroutine(req_id):
global current_request_id_global
# set value
current_request_id_ctx.set(req_id)
current_request_id_global = req_id
await some_inner_coroutine()
# get value
print('Processed outer coroutine of request: {}\n'.format(current_request_id_ctx.get()))
async def main():
tasks = []
for req_id in range(1, 10000):
tasks.append(asyncio.create_task(some_outer_coroutine(req_id)))
await asyncio.gather(*tasks)
if __name__ == '__main__':
asyncio.run(main())
输出:
...
Processed inner coroutine of request: 458
ERROR! global var=9999
Processed outer coroutine of request: 458
Processed inner coroutine of request: 459
ERROR! global var=9999
Processed outer coroutine of request: 459
...
可以在 PЕP 567
中找到使用threading.local()
转换代码的示例