在关于asyncio的基本用法中提到,asyncio并不是多线程。在协程中调用同步(阻塞函数),都占用同一线程的CPU时间,即当前线程会被阻塞(即协程只会在等待一个协程时可能出让CPU,如果是普通函数,它是不会出让CPU的,会一直执行直到完成,或者被其它线程中断)。

如果我们依赖的某个第三方库并不是异步的,那么对其API的调用也会阻塞住。如果这个第三方库是网络IO请求密集型的,那么是可以通过多线程甚至多进程封装,从而将其改造成异步库的。

本文提供了通过concurrent.futures库来实现多线程异步封装的思路和实现。

concurrent.futures

这个包提供了线程池和进程池的实现。从Python 3.5以后,asyncio提供了loop.run_in_executor的实现,将asyncio的协程与concurrent.futures的future连接起来的方法。这样我们自己就不用去实现线程池,信号机制、返回值的传递机制了。

我们这里不仔细分析两者的连接及内部机制,只通过一个例子来展示如何使用:

from concurrent.futures import ThreadPoolExecutor

import time

import asyncio

def work():

time.sleep(5)

return 'done'

async def main(loop):

executor = ThreadPoolExecutor()

result = await loop.run_in_executor(executor, work)

print(result)

loop = asyncio.get_event_loop()

loop.run_until_complete(main(loop))

loop.close()

上面的代码已经很清楚了。代码定义了一个线程池executor,通过loop.run_in_executor,将同步调用work转化成异步调用,并且work的返回值也一并传递出来。

整个代码段都是异步函数风格的。如果你多调用几次await loop.run_in_executor(executor, work),就会发现代码的执行也确实是异步行为。

通过代理机制封装

明白了通过concurrent.futures来实现同步转异步的原理,理论上我们就可以依照上面的方式,将任何一个同步调用(比如上面的work),转化成异步调用了。

但如果第三方库提供了非常多的API,我们就得考虑更优美的实现方式,以减少重复代码量。这里我们使用代理机制。

首先我们来看一个特别的函数, getattr(self, name)。如果我们有一个类对象foo,通过foo来引用其属性bar时,如果bar不存在,python就会调用getattr来继续查找这个bar,如果getattr没有被我们改写,则结果仍然会是找不到,此时就会抛出熟悉的AttributeError:

AttributeError: 'Foo' object has no attribute 'bar'

我们可以利用这个特性来实现Python的对象代理。假设被代理的库名为somelib,其中提供了一个同步的网络函数send,则我们可以通过代理技术来实现一个mylib,当调用mylib.send时,最终仍然通过somelib.send来完成功能,但它是异步的。

import asyncio

from concurrent.futures import ThreadPoolExecutor

class AsyncWrapper:

def __init__(self, subject, loop=None, max_workers=None):

self.subject = subject

self.loop = loop or asyncio.get_event_loop()

self.executor = ThreadPoolExecutor(max_workers=max_workers)

def __getattr__(self, name):

origin = getattr(self.subject, name)

if callable(origin):

def foo(*args, **kwargs):

return self.run(origin, *args, **kwargs)

# cache the function we built right now, to avoid later lookup

self.__dict__[name] = foo

return foo

else:

return origin

async def run(self, origin_func, *args, **kwargs):

def wrapper():

return origin_func(*args, **kwargs)

return await self.loop.run_in_executor(self.executor, wrapper)

这里我实现了一个非常简单的异步封装器AsynWrapper。构造函数接受三个参数,第一个为要代理的对象主体,在我们的例子中即为somelib。第二个是event loop对象,如果不提供,则会自动生成。第三个是初始化线程池所需要的。

这里要注意event loop对象尽管是可选的,但如果你的程序是多线程的,则必须在主线程中获取event loop对象并将其传递过来。因为每个线程都有自己的event loop,它们之间无法同步。

改写的getattr是我们实现魔法的地方。假设我们通过AsyncWrapper生成了一个对象foo,则在foo上调用send函数时:

await foo.send(...)

当foo.send()被调用时,究竟发生了什么?可以认为这里发生了两件事,第一件事是要找到foo.send这个函数对象,其次是要对它进行调用。看起来比较啰嗦,但却是理解我们封装的关键。

我们先看查找。

由于foo本身是没有send这个属性的,因此getattr被调用,并且传入了name = 'send'。我们先检查这个send是否是原来lib中的一个函数,因为我们没有必要也不应该拦截属性:

origin = getattr(self.subject, name)

if callable(origin):

#替换

else:

return origin

因此如果send是somelib中的一个属性(比如常量),我们直接返回其值。但如果它是一个可执行对象,那么我们将其封装成一个异步函数。

如果send是一个函数呢?我们当然不能直接返回它,而应该返回另一个函数,在这个函数里,它将在executor中执行origin,从而实现异步化。这个函数就是self.run:

async def run(self, origin_func, *args, **kwargs):

def wrapper():

return origin_func(*args, **kwargs)

return await self.loop.run_in_executor(self.executor, wrapper)

这里的内联函数wrapper只是为了将参数封装,因为run_in_executor只接受位置参数(args),而不接受可选参考(*kwargs)。

现在问题来了,如何在getattr中返回run对象,并且这个run对象知道应该执行哪一个origin函数呢?这就是内联函数foo的作用。它将origin原本应该有的参数,以及origin本身一起打包:

def foo(*args, **kwargs):

return self.run(origin, *args, **kwargs)

最后要提到的就是这一行:

self.__dict__[name] = foo

这是一种优化。如此以来,下一次我们再调用foo.send时,getattr就不会再调用了,因为send已经成为foo的一个方法。

Demo

import somelib

async main():

foo = AsyncWrapper(somelib)

await foo.send("hello world!")

其它

除了getattr外,python还提供了getattribute函数。两者的区别是,后者无论如何(即在foo中有send属性时)都会被调用。考虑到我们的目的,这里当然使用getattr。

python如何封装成可调用的库_在python中如何以异步的方式调用第三方库提供的同步API...相关推荐

  1. python封装成exe后运行失败_解决Python使用pyinstaller打包生成exe运行提示错误 | kTWO-个人博客...

    最近用python写了个小的桌面程序,在本机上调试的时候,一点问题都没有,在生成exe后也可以正常打开,但是我发给舍友用的时候却突然出现的错误,运行后提示Failed to execute scrip ...

  2. 在python中模块可以封装_python 制作python包,封装成可用模块教程

    首先编写py程序: printtest.py def test(): print('print test') 将以上.py文件做成python模块,需要在相同目录下创建setup.py文件,setup ...

  3. python能封装成exe文件_python文件封装成*.exe文件(单文件和多文件)

    环境:win10 64位  python3.7 单*.py文件打包 Python GUI:程序打包为exe 一.安装Pyinstaller,命令pip install Pyinstaller,(大写的 ...

  4. Tools_将Python脚本封装成exe可执行文件

    将Python脚本封装成exe可执行文件 将Python脚本封装成exe可执行文件 cx_freeze是用来将 Python 脚本封装成可执行程序的工具,支持最新的Python3.2版本.生成的执行文 ...

  5. python如何封装成exe

    python文件封装成exe 第一种:.py文件直接封装成exe 第二种:整个项目封装成exe ) 第一种:.py文件直接封装成exe 1.cmd进入py文件所在的目录 备注:在py文件所在的目录下, ...

  6. python批量安装第三方库_使用Python批量安装第三方库

    Python的很多功能通过第三方库实现,99%的第三方库可以通过Python自带的pip方法进行自动下载和安装.然而Python有几十万个第三方库,最常用的也有几十个.想要一次性地安装几十个常用的第三 ...

  7. python怎么写excel数据透视自动报表_使用Python生成自动报表(E

    使用Python生成自动报表(Excel)以邮件发送 数据分析师肯定每天都被各种各样的数据数据报表搞得焦头烂额,老板的,运营的.产品的等等.而且大部分报表都是重复性的工作,这篇文章就是帮助大家如何用P ...

  8. python如何封装成可调用的库_Python实现打包成库供别的模块调用

    1.创建python项目bricewulib 2.新建test_package包并创建info1类以及print_hello方法 3.为了让包的结构再复杂点,我们再在test_package下面新建一 ...

  9. c调用python第三方库_用 Python ctypes 来调用 C/C++ 编写的第三方库

    看到一篇简洁的文章--如何用Python ctypes调用C++(ctypes调用C没这么多麻烦事),不敢独享... 如果需要用 Python 调用 C/C++ 编写的第三方库(这些第三方库很可能就是 ...

最新文章

  1. 使用sudo进入root权限,以及防止root密码被恶意篡改
  2. 模糊选择器 js_5个很棒的 React.js 库,值得你亲手试试!
  3. 计算机组成原理中英对照篇,信息科学系课程介绍(中英对照).doc
  4. python没有菜单栏_解决Jupyter Notebook开始菜单栏Anaconda下消失的问题
  5. 集合的体系结构 0119
  6. 工业机器人 答案 韩建海_探秘沈阳高科技机器人产业,玩转辽宁科技馆体验感爆棚...
  7. ubuntu18安装node.js以及npm提速(通过nvm安装20191129)
  8. linux 大文本文件,Linux文本文件处理(1)
  9. 字节跳动算法工程师总结:java资料文件
  10. linux内核虚拟内存之高端物理内存与非连续内存分配
  11. 示波器使用方法入门之道
  12. 数据分析案例--淘宝用户行为分析
  13. Android自定义View、ViewGroup
  14. 按蚂蚁金服面试不过,就因为不会RPC服务超时排查思路?
  15. AndroidStudio打包成APK安装运行闪退的解决方法
  16. 浅显易懂——泰勒展开式
  17. 怎么把ppt弄成链接的形式_ppt链接excle表格:如何在ppt中超级链接到指定的excel工作表...
  18. php调用微信公众号支付接口,Thinkphp微信公众号支付接口
  19. java中的守护线的应用_JVM中的守护线程示例详解
  20. GitHub的Java面试项目

热门文章

  1. java enum判断_Java Enum枚举 遍历判断 四种方式(包括 Lambda 表达式过滤)
  2. android 宏替换_android 模拟宏定义,实现Debug amp; Release 模式-一团网
  3. 7种从头开始免费学习编程的方法
  4. FTP搭建网络yum源
  5. linux 编译文件mm,Linux编译C++文件,说没有找到头文件,怎么啊?新手,不太会用...
  6. 需求分析的过程是什么?_7大需求分析方法与5大分析过程
  7. python 倒叙 数组_打基础一定要吃透这12类 Python 内置函数
  8. Python基础数据类型之字符串(二)
  9. 数据库系统概论:第五章 数据库完整性
  10. android封装多肽,深度探索C++对象模型之(四)...-Android.animation cts fail-Rails helper_169IT.COM...