21天挑战学习-Day08生成器


活动地址:CSDN21天学习挑战赛

学习的最大理由是想摆脱平庸,早一天就多一份人生的精彩;迟一天就多一天平庸的困扰。各位小伙伴,如果您:
想系统/深入学习某技术知识点…
一个人摸索学习很难坚持,想组团高效学习…
想写博客但无从下手,急需写作干货注入能量…
热爱写作,愿意让自己成为更好的人…

一、生成器——特殊的迭代器

1.创建生成器的方式

a.推导式的方式

# 生成器是一个特殊的迭代器
num = [x for x in range(10)]
print(num)  # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
num = (x for x in range(10))  # 生成了一个对象---->生成器
print(num)  # <generator object <genexpr> at 0x000001D532D85B60>

for i in num:
    print(i)
"""
0
1
2
3
4
5
6
7
8
9
"""
# 以上两个方式一个返回方式占用空间(推导式),一个返回方式是生成值的方法节省空间(生成器)

以上使用推导式创建的返回值num是一个列表变量储存了所有的值,而以生成器的方式即把列表变成元组最后返回给变量num的是一个这个函数的引用当for循环遍历的时候循环一次就往这个函数的里取一次,直至全部取完。

b.yield函数的使用

def create_num(all_num):
    a, b = 0, 1
    for i in range(all_num):
        # print(a)
        yield a  # 函数里面只要有yield就不是函数了而是一个生成器模板
        # 当程序运行到yield a时程序会暂停在这把a的值返回给num,然后打印num
        # 接下来继续执行下面的代码a, b = b, a + b
        a, b = b, a + b


# 如果在调用create_num()的时候,
# 发现这个函数里面有yield那么此时不是调用函数而是创建一个生成器对象

obj = create_num(10)  # 生成器
# iter()  # 可迭代对象放进去生成迭代器
# next()  # 调用迭代器里面的__next__开始取值
ret = next(obj)  # yield的值第一个为0故直接这样打印0
print(ret)  # 0ret = next(obj)  # yield的值第一个为0故直接这样打印0
ret = next(obj)  # yield的值第二个为1故直接这样打印1
print(ret)  # 1

for num in obj:
    print(num)
# 除了第一个是从开头执行的,其他的都是从暂停的地方开始执行

讲解:

1.yield a函数里面只要有yield就不是函数了而是一个生成器模板(创建一个生成器对象)
2.当程序运行到yield a时程序会暂停在这把a的值返回给num,然后打印num
3.接下来继续执行下面的代码a, b = b, a + b

def create_num(all_num):
    a, b = 0, 1
    for i in range(all_num):
        print(f'对象里面直接输出{a}')
        yield a
        a, b = b, a + b


obj = create_num(10)  # 创建生成器
# iter()  # 可迭代对象放进去生成迭代器
# next()  # 调用迭代器里面的__next__开始取值
ret = next(obj) 
print(f'ret取值得到{ret}')  # ret = next(obj)  
ret = next(obj)  
print(f'ret取值得到{ret}')  




 最后版本:

def create_num(all_num):
    a, b = 0, 1
    for i in range(all_num):
        # print(a)
        yield a  # 函数里面只要有yield就不是函数了而是一个生成器模板
        # 当程序运行到yield a时程序会暂停在这把a的值返回给num,然后打印num
        # 接下来继续执行下面的代码a, b = b, a + b
        a, b = b, a + b


obj = create_num(10)  # 生成器

for num in obj:
    print(num)
# 除了第一个是从开头执行的,其他的都是从暂停的地方开始执行

在for遍历的时候就和上面分析的一样(使用next()进行取值取一次打印一次然后直到全部取完)

 研究:

def create_num(all_num):
    a, b = 0, 1
    for i in range(all_num):
        # print(a)
        yield a
        a, b = b, a + b


obj = create_num(10)
obj2 = create_num(2)

ret = next(obj)
print("obj:", ret)
ret = next(obj)
print("obj:", ret)
ret = next(obj2)
print("obj2:", ret)
ret = next(obj)
print("obj:", ret)
# 结论:两个互不干扰


for num in obj:
    print(num)

 2.通过异常判断生成器的结束

def create_num(all_num):
    a, b = 0, 1
    for i in range(all_num):
        # print(a)
        yield a
        a, b = b, a + b
    return "---ok取完了---"  # 函数返回值不会影响


obj2 = create_num(2)
while True:
    try:
        ret = next(obj2)
        print("obj2:", ret)  # 不会受return影响
    except Exception as ret:
        print(ret)
        print(ret.value)  # 打印return方法加此句代码
        break
# 生成器是一个特定的迭代器





 3.使用send()方式来启动生成器

def create_num(all_num):
    a, b = 0, 1
    for i in range(all_num):
        ret = yield a  # 右边实现把a的值传出去,左边是赋值语句你send(“hh”),那么yield a 就是hh
        print(">>>ret>>>", ret)
        a, b = b, a + b


obj2 = create_num(2)
ret = next(obj2)
print(ret)
ret = obj2.send(None)  # send不要放在开头放在开头没有数字来接收send()里面的数据第一次可以send(None)
print(ret)
"""
可以用next()和send()方式区别在于send()方式可以传递参数
"""




 

1.可以用next()和send()方式区别在于send()方式可以传递参数

2.send不要放在开头放在开头没有数字来接收send()里面的数据

 二、对生成器总结

迭代器保存的是生成的代码而不是结果
生成器:让函数暂停执行并返回一定数据,还有就是继续执行时对接暂停执行
生成器是特殊的迭代器

三、使用协程完成多任务

1.使用yield完成

import time


def task_1():
    while True:
        print("----work1---")
        time.sleep(0.1)
        yield


def task_2():
    while True:
        print("----work2---")
        time.sleep(0.1)
        yield


def main():
    t1 = task_1()
    t2 = task_2()
    while True:
        next(t1)
        next(t2)


if __name__ == "__main__":
    main()

# 并发
# 进程要创建资源啥的比线程浪费,而协程比线程更好

 运行结果:

----work1---
----work2---
----work1---
----work2---
----work1---
----work2---
----work1---
----work2---
----work1---
----work2---
----work1---
----work2---
...省略...

进程要创建资源啥的比线程浪费,而协程比线程更好

2.使用greenlet完成多任务

from greenlet import greenlet
import time


def test1():
    while True:
        print("---A---")
        gr2.switch()
        time.sleep(0.5)


def test2():
    while True:
        print("---B---")
        gr1.switch()
        time.sleep(0.5)


gr1 = greenlet(test1)
gr2 = greenlet(test2)

# 切换到gr1中运行
gr1.switch()
# 这个对yield进行了封装

 原理没变只是通过greenlet对其进行了封装

 3.使用gevent完成多任务

import gevent


def f(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        # 必须全部重构手动把time.sleep()(耗时操作)转变为gevent.sleep(0.5)
        gevent.sleep(0.5)  # 遇到延时操作自动切换,协程利用了原来等待耗时操作的时间去做其他的事情


g1 = gevent.spawn(f, 5)  # 拿greenlet进行封装
g2 = gevent.spawn(f, 5)  # (执行哪个函数, 传入函数的参数)
g3 = gevent.spawn(f, 5)  # 创建了对象并不会马上执行
g1.join()  # 等待g1完成,耗时自动切换进去
g2.join()
g3.join()  # 协程依赖于线程
# <Greenlet at 0x238c9a967a0: f(5)> 0
# <Greenlet at 0x238c9a96b00: f(5)> 0
# <Greenlet at 0x238c9a972e0: f(5)> 0
# <Greenlet at 0x238c9a967a0: f(5)> 1
# <Greenlet at 0x238c9a96b00: f(5)> 1
# <Greenlet at 0x238c9a972e0: f(5)> 1
# <Greenlet at 0x238c9a967a0: f(5)> 2
# <Greenlet at 0x238c9a96b00: f(5)> 2
# <Greenlet at 0x238c9a972e0: f(5)> 2
# <Greenlet at 0x238c9a967a0: f(5)> 3
# <Greenlet at 0x238c9a96b00: f(5)> 3
# <Greenlet at 0x238c9a972e0: f(5)> 3
# <Greenlet at 0x238c9a967a0: f(5)> 4
# <Greenlet at 0x238c9a96b00: f(5)> 4
# <Greenlet at 0x238c9a972e0: f(5)> 4


讲解:

可以看到遇到耗时操作如sleep(0.5)程序不停留在那等而是先跳过(利用这部分时间)

 使用总结:

import gevent

g1 = gevent.spawn(f, 5)  # 创建实例对象 gevent.spawn(函数名,传入的参数)
g1.join()  # 开始执行

4.gevent打补丁

import gevent
import time
from gevent import monkey
monkey.patch_all()  # 将所有的耗时操作都自动检测并换为gevent里面的模块


def f(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        # 必须全部重构手动把time.sleep()(耗时操作)转变为gevent.sleep(0.5)
        # 那如何不手动改这些耗时操作的代码让他自己自动改呢
        time.sleep(0.5)  # 用的是原来的time但是monkey.patch_all()会把耗时操作全部改


g1 = gevent.spawn(f, 5)
g2 = gevent.spawn(f, 5)
g3 = gevent.spawn(f, 5)
# g1.join()
# g2.join()
# g3.join()
gevent.joinall([g1, g2, g3])  # 与上式注释的效益一样

对于耗时的函数调用需要使用gevent所规定的(如time.sleep(0.5)),这样太烦了

from gevent import monkey
monkey.patch_all()

以上代码自动化检测耗时代码并换成gevent的模板

 四、应用

1.图片下载器

import urllib.request
import gevent

count = 0


def down_load(url, name):
    req = urllib.request.urlopen(url)
    img_content = req.read()
    with open(name, "wb") as file:
        file.write(img_content)


def main():
    gevent.joinall([
        gevent.spawn(down_load, "https://img1.baidu.com/it/u=3079431989,3499240801&amp;fm=253&amp;app=138&amp;size=w931&amp;n=0&amp;f=JPEG&amp;fmt=auto?sec=1657299600&amp;t=2a1f03f85b3c4fd52005ab1e0d3cf6d4", "2.jpg"),
        gevent.spawn(down_load,"https://img0.baidu.com/it/u=2810806728,4166654119&amp;fm=253&amp;fmt=auto&amp;app=138&amp;f=JPEG?w=500&amp;h=500", "3.jpg")
    ])


if __name__ == "__main__":
    main()
# 网络下载是一个耗时过程

五、对多任务的总结

进程是资源分配的单位
线程是操作系统调度的单位
进程切换需要的资源很最大,效率很低
线程切换需要的资源一般,效率一般(当然了在不考虑GIL的情况下)
协程切换任务资源很小,效率高
多进程、多线程根据cpu核数不一样可能是并行的,但是协程是在一个线程中所以是并发

六、预告

明天更新正则表达式,最近因练车所以有点耽误了。

我们现在是被前辈们的光所照耀,未来将会是我们照耀后辈。

一步一步来,加油。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

『Knight』

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值