本文是写给JavaScript程序员的Python教程。
历史上,Python并不支持专门的异步编程语法,因为不需要。
但是,多线程有"线程竞争"的问题,处理起来很复杂,还涉及加锁。对于简单的异步任务来说(比如与网页互动),写起来很麻烦。
Python3.4引入了asyncio模块,增加了异步编程,跟JavaScript的async/await极为类似,大大方便了异步任务的处理。它受到了开发者的欢迎,成为从Python2升级到Python3的主要理由之一。
asyncio模块最大特点就是,只存在一个线程,跟JavaScript一样。
由于只有一个线程,就不可能多个任务同时运行。asyncio是"多任务合作"模式(cooperativemultitasking),允许异步任务交出执行权给其他任务,等到其他任务完成,再收回执行权继续往下执行,这跟JavaScript也是一样的。
表面上,这是一个不合理的设计,明明有多线程多进程的能力,为什么放着多余的CPU核心不用,而只用一个线程呢?但是就像前面说的,单线程简化了很多问题,使得代码逻辑变得简单,写法符合直觉。
下面介绍asyncio模块最主要的几个API。注意,必须使用Python3.7或更高版本,早期的语法已经变了。
第一步,import加载asyncio模块。
importasyncio第二步,函数前面加上async关键字,就变成了async函数。这种函数最大特点是执行可以暂停,交出执行权。
asyncdefmain():第三步,在async函数内部的异步任务前面,加上await命令。
awaitasyncio.sleep(1)上面代码中,asyncio.sleep(1)方法可以生成一个异步任务,休眠1秒钟然后结束。
执行引擎遇到await命令,就会在异步任务开始执行之后,暂停当前async函数的执行,把执行权交给其他任务。等到异步任务结束,再把执行权交回async函数,继续往下执行。
第四步,async.run()方法加载async函数,启动事件循环。
asyncio.run(main())上面代码中,asyncio.run()在事件循环上监听async函数main的执行。等到main执行完了,事件循环才会终止。
下面是async函数的例子,新建一个脚本async.py,代码如下。
#!/usr/bin/envpython3#async.pyimportasyncioasyncdefcount():print("One")awaitasyncio.sleep(1)print("Two")asyncdefmain():awaitasyncio.gather(count(),count(),count())asyncio.run(main())上面脚本中,在async函数main的里面,asyncio.gather()方法将多个异步任务(三个count())包装成一个新的异步任务,必须等到内部的多个异步任务都执行结束,这个新的异步任务才会结束。
脚本的运行结果如下。
作为对比,下面是这个例子的同步版本sync.py。
#!/usr/bin/envpython3#sync.pyimporttimedefcount():print("One")time.sleep(1)print("Two")defmain():for_inrange(3):count()main()上面脚本的运行结果如下。
最后是一个异步编程的真实例子:操作无头浏览器。异步编程对代码的简化,在这个例子体现得淋漓尽致。
$python3-mpipinstallpyppeteer然后,写一个网页截图脚本screenshot.py。
执行这个脚本,当前目录下就会生成截图文件example.png。
(正文完)
Python是当下最火的编程语言,房地产大佬潘石屹都说要学。
不过,要写出更复杂的应用,或者从事数据分析、机器学习、Web开发等工作,就需要正规系统的学习了。建议从一个简单的小项目开始,然后不断完善功能,去学习更多新东西。
完成以上四步,就从一个初级Python使用者成长为一名熟练工了。当然说起来简单,真正实践起来并不容易。每一步都会有比较多的坑,对于没有经验的人来说,自学效率比较低。如果有一个经验丰富的老师带,效果会好很多。
(完)
KevinBlandy说:
james说:
screenshot.py这个例子中,虽然五个步骤可以异步处理,但它们之间明显有前后关系。也可以吗?
乐亦栗说:
nodeserver说:
引用james的发言:screenshot.py这个例子中,虽然五个步骤可以异步处理,但它们之间明显有前后关系。也可以吗?
zhaifg说:
"下面介绍asyncio模块最主要的几个API。注意,必须使用Python3.7或更高版本,早期的语法已经变了。"这个地方应该是从Python3.5起加入了await,不是3.7
softbase说:
这个栗子说明了什么呢?比起同步线程有任何优势吗?还是仅仅为了说明语法。
Pelops.Yao说:
那几张配图真好看!
花满楼说:
引用zhaifg的发言:"下面介绍asyncio模块最主要的几个API。注意,必须使用Python3.7或更高版本,早期的语法已经变了。"这个地方应该是从Python3.5起加入了await,不是3.7
没有说*await*,asyncio.run()是3.7才加上的
screamff说:
主要要自己分辨哪些方法、函数能用asyncio,哪些不能,比如requests就不能用原生asyncio,老司机用起来才会轻车熟路一点
kiwiyan说:
哈哈,虽然带硬广,但对初学者的指导作用还是不错的
yangzx说:
引用screamff的发言:主要要自己分辨哪些方法、函数能用asyncio,哪些不能,比如requests就不能用原生asyncio,老司机用起来才会轻车熟路一点
QAQ说:
ponder说:
asyncio模块最大特点就是,只存在一个线程-------------------------------------------
这里确切地说,只有一个eventloop线程,threading.Thread还是能正常使用的。
Jason说:
gaspar08说:
正是因为有前后关系,才用到await
MakaBaka说:
featue说:
本来也觉得没什么卵用,直到我去掉async和await后,一运行就报错了。~~~~