Python(三十八)python协程

71 阅读4分钟

协程不是由计算机提供,而是程序员人为创造的。

协程(Coroutine),也可以被称为微线程,是一种用户态内的上下文切换技术吗,简而言之,其实就是通过一个线程实现代码块相互切换执行。

#!/usr/bin/python3
# -*- coding: utf-8 -*-
@Time    : 2022/3/17 19:38
@Author  : camellia
@Email   : 805795955@qq.com
@File    : coroutine.py
@Software: PyCharm
# 测试协程程序

def fun1():
    print('我在方法fun1中!')

def fun2():
    print('我在方法fun2中……')

fun1()
fun2()

实现协程大概有以下这么几种方法:

1.      Greenlet,早期模块

2.      Yield关键字

3.      Asyncio装饰器(python3.4以上才有)

4.      Async await关键字(python3.5以上才有)

 

一:greenlet(比较老旧,不建议使用)

1:安装greenlet

pip3 install greenlet

 

2:基本使用

#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Time    : 2022/3/17 19:38
# @Author  : stone
# @Email   : 805795955@qq.com
# @File    : coroutine.py
# @Software: PyCharm
# 测试协程程序
from greenlet import greenlet
def test1():
    print(12)           # 第二步 输出12
    gr2.switch()        # 第三步 切换test2函数
    print(34)           # 第六步 输出34
    gr2.switch()        # 第七步 切换test2函数

def test2():
    print(56)           # 第四步 输出56
    gr1.switch()        # 第五步 切换test1函数
    print(78)           # 第八步 输出78
    gr1.switch()

gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()            # 第一步 执行test1函数

执行上边的代码,输出:

12
56
34
78

具体输出步骤请参照代码中的注释。

 

二:yield关键字(生成器)(比较老旧,不建议使用)

#!/usr/bin/python3
# -*- coding: utf-8 -*-
@Time    : 2022/3/17 19:38
@Author  : camellia
@Email   : 805795955@qq.com
@File    : coroutine.py
@Software: PyCharm
# 测试协程程序

def fun1():
    yield 1             # 第二步 输出 1
    yield from fun2()   # 第三步调用fun2
    yield 2             # 第六步 输出 2

def fun2():
    yield 3             # 第四步 输出 3
    yield 4             # 第五步 输出 4

f1 = fun1()             # 第一步调用fun1
for item in f1:
    print(item)

执行上边代码输出:

1
3
4
2

具体输出步骤请参照代码中的注释。

 

三:asyncio(>=python3.4)

Python3.4以上的版本还可以使用asyncio来实现协程,具体代码如下:

#!/usr/bin/python3
# -*- coding: utf-8 -*-
@Time    : 2022/3/17 19:38
@Author  : camellia
@Email   : 805795955@qq.com
@File    : coroutine.py
@Software: PyCharm
# 测试协程程序
import asyncio

@asyncio.coroutine
def fun1():
    print(1)
    yield from asyncio.sleep(1)
    print(2)

@asyncio.coroutine
def fun2():
    print(3)
    yield from asyncio.sleep(1)
    print(4)

# 任务列表
tasks = [
    asyncio.ensure_future(fun1()),
    asyncio.ensure_future(fun2())
]

# 时间循环
loop = asyncio.get_event_loop()
# 执行
loop.run_until_complete(asyncio.wait(tasks))

执行上边的代码,输出:

1
3
2
4

Asyncio这个就比较厉害了,他是自动切换执行方法的。上边我们看到的yield与greenled都是我们人为控制的。这就很高级。

 

四:async await关键字(>=python3.5)(推荐使用)

Python3.5以上才可以使用async与await,这两个关键字其实就是替代我们上边使用到的yield关键字,因此我们只需要将上方的程序做一下小修改即可:

#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Time    : 2022/3/17 19:38
# @Author  : camellia
# @Email   : 805795955@qq.com
# @File    : coroutine.py
# @Software: PyCharm
# 测试协程程序
import asyncio

async def fun1():
    print(1)
    await asyncio.sleep(1)
    print(2)

async def fun2():
    print(3)
    await asyncio.sleep(1)
    print(4)

# 任务列表
tasks = [
    asyncio.ensure_future(fun1()),
    asyncio.ensure_future(fun2())
]

# 时间循环
loop = asyncio.get_event_loop()
# 执行
loop.run_until_complete(asyncio.wait(tasks))

 

以上大概就是python中四种实现协程的方式。

 

五:协程的意义

在线程中如果遇到IO等待时间,线程不会去傻等,利用空闲的时候再去做点其他的事。

干说没有代码实现来的醒目。下边我们使用一个例子来展示一下:

下载三张图片,第一种实现方式,使用正常的同步下载。

第二种实现方式使用协程来下载。对比下载时间。

同步爬取:

#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Time    : 2022/3/17 19:38
# @Author  : camellia
# @Email   : 805795955@qq.com
# @File    : coroutine.py
# @Software: PyCharm
# 测试协程程序
import time
import requests
import random
import string
import asyncio

start =time.clock()

def getImage():
    """
    :name 爬取网络图片
    :param url: 图片url
    :param num: 图片名称
    :return: 无返回值
    """
    lis = [
        "https://resource.guanchao.site/uploads/sowing/welcome-image3.jpg",
        "https://resource.guanchao.site/uploads/sowing/welcome-image7.jpg",
        "https://resource.guanchao.site/uploads/sowing/welcome-image5.jpg"
    ]
    for item in lis:
        # 1:指定url
        # url = "https://resource.guanchao.site/uploads/sowing/welcome-image3.jpg"
        # 2:模拟网络请求链接
        responce = requests.get(url=item)
        print('爬取:'+item)
        # 3:获取响应数据,content获取二进制数据
        content = responce.content
        filename = './img/' + ''.join(random.sample(string.ascii_letters + string.digits, 8)) + '.jpg'
        # 4:持久化存储
        with open(filename, 'wb'as fe:
            fe.write(content)
            print('爬取完成')

getImage()

end = time.clock()
print('Running time: %s Seconds'%(end-start))

输出:

爬取:https://resource.guanchao.site/uploads/sowing/welcome-image3.jpg
爬取完成
爬取:https://resource.guanchao.site/uploads/sowing/welcome-image7.jpg
爬取完成
爬取:https://resource.guanchao.site/uploads/sowing/welcome-image5.jpg
爬取完成
Running time: 2.4804463 Seconds

 

协程爬取:

协程爬取使用aiohttp模块

#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @Time    : 2022/3/17 19:38
# @Author  : camellia
# @Email   : 805795955@qq.com
# @File    : coroutine.py
# @Software: PyCharm
# 测试协程程序
import time
import requests
import random
import string
import asyncio
import aiohttp

start =time.clock()

async def fetch(session,url):
    print('发送请求:'+url)
    async with session.get(url,verify_ssl=Falseas response:
        content = await response.content.read()
        filename = './img/' + ''.join(random.sample(string.ascii_letters + string.digits, 8)) + '.jpg'
        # 4:持久化存储
        with open(filename, 'wb'as fe:
            fe.write(content)
            print('爬取完成')

async def mains():
    async with aiohttp.ClientSession() as session:
        lis = [
            "https://resource.guanchao.site/uploads/sowing/welcome-image3.jpg",
            "https://resource.guanchao.site/uploads/sowing/welcome-image7.jpg",
            "https://resource.guanchao.site/uploads/sowing/welcome-image5.jpg"
        ]
        tasks = [asyncio.create_task(fetch(session,url)) for url in lis]
        await asyncio.wait(tasks)

# asyncio.run(mains())
loop = asyncio.get_event_loop()
loop.run_until_complete(mains())

执行代码:

发送请求:https://resource.guanchao.site/uploads/sowing/welcome-image3.jpg
发送请求:https://resource.guanchao.site/uploads/sowing/welcome-image7.jpg
发送请求:https://resource.guanchao.site/uploads/sowing/welcome-image5.jpg
爬取完成
爬取完成
爬取完成
Running time: 1.8801726 Seconds

 

我们可以根据 输出的文字顺序看到代码是如何执行的。并且执行速度更快。

 

有好的建议,请在下方输入你的评论。