我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Python协程中使用上下文

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Python协程中使用上下文

在Python 3.7中,asyncio 协程加入了对上下文的支持。使用上下文就可以在一些场景下隐式地传递变量,比如数据库连接session等,而不需要在所有方法调用显示地传递这些变量。使用得当的话,可以提高接口的可读性和扩展性。

协和的上下文是通过 contextvars 中的 ContextVar 对象来管理的。最基本的使用方式是在某一调用层次中设置上下文,然后在后续调用中使用。如下例所示:

import asyncio
import contextvars
from random import randint
from unittest import TestCase

request_id_context = contextvars.ContextVar('request-id')


async def inner(x):
    request_id = request_id_context.get()
    if request_id != x:
        raise AssertionError('request_id %d from context does NOT equal with parameter x %d' % (request_id, x))

    print('start handling inner request-%d, with x: %d' % (request_id, x))
    await asyncio.sleep(randint(0, 3))
    print('finish handling inner request-%d, with x: %d' % (request_id, x))


async def outer(i):
    print('start handling outer request-%d' % i)
    request_id_context.set(i)
    await inner(i)
    print('finish handling outer request-%d with request_id in context %d' % (i, request_id_context.get()))


async def dispatcher():
    await asyncio.gather(*[
        outer(i) for i in range(0, 10)
    ])


class ContextTest(TestCase):

    def test(self):
        asyncio.run(dispatcher())

上例中,在最后定义了一个单元测试用例对象 ContextTest 。它的方法 test 是程序的入口,使用 asyncio.run 方法来在协程中执行被测试的异步方法 dispatcherdispatcher 则并发启动10个异步方法 outerouter方法首先将在模块层定义的上下文变量 request_id_context 设置为当前调用指定的值,这个值对于每个 outer 的调用都是不同的。 然后在后续被调用的 inner 方法,以及 outer 方法内部访问了这个上下文变更。在 inner 方法内容,则比较了显示传入的 i 和从上下文变量中取出的 request_id

测试用例的执行结果如下:

start handling outer request-0
start handling inner request-0, with x: 0
start handling outer request-1
start handling inner request-1, with x: 1
start handling outer request-2
start handling inner request-2, with x: 2
start handling outer request-3
start handling inner request-3, with x: 3
start handling outer request-4
start handling inner request-4, with x: 4
start handling outer request-5
start handling inner request-5, with x: 5
start handling outer request-6
start handling inner request-6, with x: 6
start handling outer request-7
start handling inner request-7, with x: 7
start handling outer request-8
start handling inner request-8, with x: 8
start handling outer request-9
start handling inner request-9, with x: 9
finish handling inner request-3, with x: 3
finish handling outer request-3 with request_id in context 3
finish handling inner request-7, with x: 7
finish handling outer request-7 with request_id in context 7
finish handling inner request-1, with x: 1
finish handling outer request-1 with request_id in context 1
finish handling inner request-4, with x: 4
finish handling outer request-4 with request_id in context 4
finish handling inner request-5, with x: 5
finish handling outer request-5 with request_id in context 5
finish handling inner request-9, with x: 9
finish handling outer request-9 with request_id in context 9
finish handling inner request-0, with x: 0
finish handling outer request-0 with request_id in context 0
finish handling inner request-2, with x: 2
finish handling outer request-2 with request_id in context 2
finish handling inner request-6, with x: 6
finish handling outer request-6 with request_id in context 6
finish handling inner request-8, with x: 8
finish handling outer request-8 with request_id in context 8

可以看到,虽然每次 outer 方法对模块层同定义的同一个上下文变量 request_id_context 设置了不同的值,但后续并发访问相互之间并不会混淆或冲突。

前一节展示了在设置了上下文变量后,在后续使用中读取这个变量的情况。这一节,我们看一下不用调用层次间对同一个上下文变量进行修改的情况。

在上一节代码上做了一些调整后如下:

import asyncio
import contextvars
from random import randint
from unittest import TestCase

request_id_context = contextvars.ContextVar('request-id')

obj_context = contextvars.ContextVar('obj')


class A(object):

    def __init__(self, x):
        self.x = x

    def __repr__(self):
        return '<A|x: %d>' % self.x


async def inner(x):
    request_id = request_id_context.get()
    if request_id != x:
        raise AssertionError('request_id %d from context does NOT equal with parameter x %d' % (request_id, x))

    print('start handling inner request-%d, with x: %d' % (request_id, x))
    request_id_context.set(request_id * 10)
    await asyncio.sleep(randint(0, 3))

    obj = A(x)
    obj_context.set(obj)
    print('finish handling inner request-%d, with x: %d' % (request_id, x))


async def outer(i):
    print('start handling outer request-%d with request_id in context %d' % (i, request_id_context.get()))
    request_id_context.set(i)
    await inner(i)
    print('obj: %s in outer request-%d' % (obj_context.get(), i))
    print('finish handling outer request-%d with request_id in context %d' % (i, request_id_context.get()))


async def dispatcher():
    request_id_context.set(-1)
    await asyncio.gather(*[
        outer(i) for i in range(0, 10)
    ])
    print('finish all coroutines with request_id in context: %d' % (request_id_context.get()))


class ContextTest(TestCase):

    def test(self):
        asyncio.run(dispatcher())

具体调整

  1. dispatcher 中,开始启动协程前,将 request_id_context 设置为 -1 。 然后在所有的协程调用完毕后,再查看 request_context_id 的值。
  2. outer 中,在设置 request_id_context 之前,先查看它的值。
  3. inner 中,在检查和查看 request_id_context 之后,将它修改为其原始值的10倍。
  4. 定义了一个对象 A ,以及一个用来传递 A 对象实例的上下文变量 obj_context
  5. inner 中,创建A的实例并保存到obj_context中。
  6. outer中,调用完inner方法后,查看obj_context上下文变量。

代码的执行结果如下:

start handling outer request-0 with request_id in context -1
start handling inner request-0, with x: 0
start handling outer request-1 with request_id in context -1
start handling inner request-1, with x: 1
start handling outer request-2 with request_id in context -1
start handling inner request-2, with x: 2
start handling outer request-3 with request_id in context -1
start handling inner request-3, with x: 3
start handling outer request-4 with request_id in context -1
start handling inner request-4, with x: 4
start handling outer request-5 with request_id in context -1
start handling inner request-5, with x: 5
start handling outer request-6 with request_id in context -1
start handling inner request-6, with x: 6
start handling outer request-7 with request_id in context -1
start handling inner request-7, with x: 7
start handling outer request-8 with request_id in context -1
start handling inner request-8, with x: 8
start handling outer request-9 with request_id in context -1
start handling inner request-9, with x: 9
finish handling inner request-6, with x: 6
obj: <A|x: 6> in outer request-6
finish handling outer request-6 with request_id in context 60
finish handling inner request-0, with x: 0
obj: <A|x: 0> in outer request-0
finish handling outer request-0 with request_id in context 0
finish handling inner request-2, with x: 2
obj: <A|x: 2> in outer request-2
finish handling outer request-2 with request_id in context 20
finish handling inner request-3, with x: 3
obj: <A|x: 3> in outer request-3
finish handling outer request-3 with request_id in context 30
finish handling inner request-5, with x: 5
obj: <A|x: 5> in outer request-5
finish handling outer request-5 with request_id in context 50
finish handling inner request-7, with x: 7
obj: <A|x: 7> in outer request-7
finish handling outer request-7 with request_id in context 70
finish handling inner request-8, with x: 8
obj: <A|x: 8> in outer request-8
finish handling outer request-8 with request_id in context 80
finish handling inner request-9, with x: 9
obj: <A|x: 9> in outer request-9
finish handling outer request-9 with request_id in context 90
finish handling inner request-1, with x: 1
obj: <A|x: 1> in outer request-1
finish handling outer request-1 with request_id in context 10
finish handling inner request-4, with x: 4
obj: <A|x: 4> in outer request-4
finish handling outer request-4 with request_id in context 40
finish all coroutines with request_id in context: -1

观察执行结果,可以看到对上下文变量的修改,有两种情况:

  1. 对于已经设置过值的上下文变量,后续对其做的修改是单向传播的。尽管每个 outer 方法都 request_id_context 设置成了不同的值,但最后在 dispatcher 调用完所有的 outer 后,它取到的 request_id_context 仍然为 -1。 同样,inner方法虽然修改了request_id_context,但这个修改对调用它的outer是不可见的。另外一个方向,outer可以读取到调用它的dispatcher修改的值,inner也可以读取到outer的修改。
  2. 如果是新设置的上下文变量,它的值可以传递到其所在方法的调用者。比如在inner中设置的obj_context,在outer中可以读取。

根据Python文档, ContextVar对象会持有变量值的强引用,所以如果没有适当清理,会导致内存漏泄。我们使用以下代码演示这种问题。

import asyncio
import contextvars
from unittest import TestCase
import weakref

obj_context = contextvars.ContextVar('obj')
obj_ref_dict = {}


class A(object):

    def __init__(self, x):
        self.x = x

    def __repr__(self):
        return '<A|x: %d>' % self.x


async def inner(x):
    obj = A(x)
    obj_context.set(obj)
    obj_ref_dict[x] = weakref.ref(obj)


async def outer(i):
    await inner(i)
    print('obj: %s in outer request-%d from obj_ref_dict' % (obj_ref_dict[i](), i))


async def dispatcher():
    await asyncio.gather(*[
        outer(i) for i in range(0, 10)
    ])
    for i in range(0, 10):
        print('obj-%d: %s in obj_ref_dict' % (i, obj_ref_dict[i]()))


class ContextTest(TestCase):

    def test(self):
        asyncio.run(dispatcher())

和上一节中的代码一样,inner方法在调用栈的最内部设置了上下文变量obj_context。不同的是,在设置上下文的同时,也将保存在上下文中的对象A的实例保存到一个弱引用中,以便后续通过弱引用来检查对象实例是否被回收。

代码的执行结果如下:

obj: <A|x: 0> in outer request-0 from obj_ref_dict
obj: <A|x: 1> in outer request-1 from obj_ref_dict
obj: <A|x: 2> in outer request-2 from obj_ref_dict
obj: <A|x: 3> in outer request-3 from obj_ref_dict
obj: <A|x: 4> in outer request-4 from obj_ref_dict
obj: <A|x: 5> in outer request-5 from obj_ref_dict
obj: <A|x: 6> in outer request-6 from obj_ref_dict
obj: <A|x: 7> in outer request-7 from obj_ref_dict
obj: <A|x: 8> in outer request-8 from obj_ref_dict
obj: <A|x: 9> in outer request-9 from obj_ref_dict
obj-0: <A|x: 0> in obj_ref_dict
obj-1: <A|x: 1> in obj_ref_dict
obj-2: <A|x: 2> in obj_ref_dict
obj-3: <A|x: 3> in obj_ref_dict
obj-4: <A|x: 4> in obj_ref_dict
obj-5: <A|x: 5> in obj_ref_dict
obj-6: <A|x: 6> in obj_ref_dict
obj-7: <A|x: 7> in obj_ref_dict
obj-8: <A|x: 8> in obj_ref_dict
obj-9: <A|x: 9> in obj_ref_dict

可以看到,无论是在outer中,还是在dispatcher中,所有inner方法保存的上下文变量都被没有被回收。所以我们必需在使用完上下文变量后,显示清理上下文,否则会导致内存泄漏。

这里,我们在inner方法的最后,将obj_context设置为None,就可以保证不会因为上下文而导致内存不会被回收:

async def inner(x):
    obj = A(x)
    obj_context.set(obj)
    obj_ref_dict[x] = weakref.ref(obj)
    obj_context.set(None)

修改后的代码执行结果如下:

obj: None in outer request-0 from obj_ref_dict
obj: None in outer request-1 from obj_ref_dict
obj: None in outer request-2 from obj_ref_dict
obj: None in outer request-3 from obj_ref_dict
obj: None in outer request-4 from obj_ref_dict
obj: None in outer request-5 from obj_ref_dict
obj: None in outer request-6 from obj_ref_dict
obj: None in outer request-7 from obj_ref_dict
obj: None in outer request-8 from obj_ref_dict
obj: None in outer request-9 from obj_ref_dict
obj-0: None in obj_ref_dict
obj-1: None in obj_ref_dict
obj-2: None in obj_ref_dict
obj-3: None in obj_ref_dict
obj-4: None in obj_ref_dict
obj-5: None in obj_ref_dict
obj-6: None in obj_ref_dict
obj-7: None in obj_ref_dict
obj-8: None in obj_ref_dict
obj-9: None in obj_ref_dict

可以看到,当outerdispatcher尝试通过弱引用来访问曾经保存在上下文中的对象实例时,这些对象都已经被回收了。

在协程中使用 contextvars 模块中的_ContextVar_对象可以让我们方便在协程间保存上下文数据。在使用时要注意以下几点:

  1. contextvars 对协程的支持是从Python 3.7才开始的,使用时要注意Python版本。
  2. ContextVar 应当在模块级别定义和创建,一定不能在闭包中定义。
  3. 保存在上下文中的变量一定要在使用完成后显示清理,否则会导致内存泄漏。
  • https://docs.python.org/3/library/contextvars.html#asyncio-support
  • https://docs.python.org/3/library/contextvars.html#contextvars.ContextVar

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Python协程中使用上下文

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Python协程中使用上下文

在Python 3.7中,asyncio 协程加入了对上下文的支持。使用上下文就可以在一些场景下隐式地传递变量,比如数据库连接session等,而不需要在所有方法调用显示地传递这些变量。使用得当的话,可以提高接口的可读性和扩展性。协和的上下
2023-01-30

Kotlin协程上下文与上下文元素深入理解

协程上下文是一个有索引的Element实例集合,每个element在这个集合里有一个唯一的key;协程上下文包含用户定义的一些数据集合,这些数据与协程密切相关;协程上下文用于控制线程行为、协程的生命周期、异常以及调试
2022-11-13

协程在Linux中的上下文恢复机制

在Linux中,协程的上下文恢复机制主要依赖于操作系统提供的上下文切换功能。在协程中,可以使用操作系统提供的函数来保存和恢复协程的上下文信息,以实现协程的切换和调度。具体来说,在Linux中,可以使用ucontext.h头文件中提供的函数
协程在Linux中的上下文恢复机制
2024-08-08

将主协程上下文的副本传递给子例程上下文

在PHP中,协程是一种强大的编程工具,可以提高代码的执行效率。而在协程中,将主协程上下文的副本传递给子例程上下文是一种常用的操作。通过这种方式,可以在子例程中访问主协程的上下文数据,实现数据的共享和传递。这一过程在PHP中非常简单,只需要使
将主协程上下文的副本传递给子例程上下文
2024-02-10

Linux协程的上下文切换机制

在Linux系统中,协程的上下文切换是通过使用setjmp和longjmp函数实现的。setjmp函数用于保存当前函数的执行环境,并返回0;longjmp函数用于恢复之前保存的执行环境,并返回到setjmp函数的调用点。这样可以实现协程在不
Linux协程的上下文切换机制
2024-08-06

怎么在python中使用上下文管理

本篇文章为大家展示了怎么在python中使用上下文管理,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。python主要应用领域有哪些1、云计算,典型应用OpenStack。2、WEB前端开发,众多大型
2023-06-14

Python中gevent模块协程使用

目录背景什么是协程?什么是 gevent?协程的例子Q&AQ:gevent 无法捕获的耗时A:猴子补丁实践异步 requests 请求gevent 的锁Tip背景因为 Python 线程的性能问题,在 Python 中使用多线程运行代码经常
2022-06-02

Python上下文管理器详细使用教程

Python有三大神器,一个是装饰器,一个是迭代器、生成器,最后一个就是今天文章的主角--「上下文管理器」。上下文管理器在日常开发中的作用是非常大的,可能有些人用到了也没有意识到这一点
2023-02-08

如何在python中使用上下文管理器

如何在python中使用上下文管理器?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。Python的优点有哪些1、简单易用,与C/C++、Java、C# 等传统语言相比,Pytho
2023-06-14

python上下文管理器协议怎么实现

这篇文章主要介绍了python上下文管理器协议怎么实现的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇python上下文管理器协议怎么实现文章都会有所收获,下面我们一起来看看吧。前言在上下文管理器协议的过程中,涉
2023-07-02

JSch中怎么使用sftp协议实现服务器文件上传下载

这篇文章主要介绍了JSch中怎么使用sftp协议实现服务器文件上传下载的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇JSch中怎么使用sftp协议实现服务器文件上传下载文章都会有所收获,下面我们一起来看看吧。J
2023-06-29

Python使用gevent实现协程

Python中多任务的实现可以使用进程和线程,也可以使用协程。  一、协程介绍  协程,又称微线程。英文名Coroutine。协程是Python语言中所特有的,在其他语言中没有。  协程是python中另外一种实现多任务的方式,比线程更小、
2023-01-31

如何在python中使用await协程函数

如何在python中使用await协程函数?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。python主要应用领域有哪些1、云计算,典型应用OpenStack。2、WEB前端开发
2023-06-14

怎么在Python中使用gevent实现协程

怎么在Python中使用gevent实现协程?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。python是什么意思Python是一种跨平台的、具有解释性、编译性、互动性和面向对象
2023-06-14

使用python上传和下载文件到Fast

1. 下载fdfs_client-py-1.2.6.tar.gz2. 解压后进入目录执行"python setup.py install":3. 新建测试文件test_fdfs.py,把下载解压后安装包的.../FastDFS/conf/c
2023-01-31

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录