【Python基础】生成器

什么是生成器

  • Python中,使用生成器可以很方便地支持迭代器协议
  • 生成器通过生成器函数产生,通过def定义,但不是通过return返回,而是通过yield一次返回一个结果,在每个结果之间挂起和恢复它们的状态,来实现迭代器协议
  • yield本质上是一个语法糖,内部是一个状态机,维护着挂起和恢复的状态,从而支持迭代器协议

生成器示例

def my_range(n):
    i = 0
    while i < n:
        yield i

        i += 1


gen_obj = my_range(3)

print(type(gen_obj))

print(next(gen_obj))
print(next(gen_obj))
print(next(gen_obj))
<class 'generator'>
0
1
2
  • 在这个例子中,定义了一个生成器函数my_range()
  • 调用生成器函数会返回一个生成器对象,本质上是返回生成器对象的迭代器,通常直接称为生成器
  • 既然生成器本质上是一个迭代器,那么其内部需要实现__iter__()方法和__next__()方法,dir()函数可以获取一个对象的所有属性和方法,可以使用dir()函数查看生成器对象的属性和方法
def my_range(n):
    i = 0
    while i < n:
        yield i

        i += 1


gen_obj = my_range(3)

print(dir(gen_obj))

print('__iter__' in dir(gen_obj))
print('__next__' in dir(gen_obj))
['__class__', '__del__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__next__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']
True
True

生成器工作流程

  • 在上面的例子中加入一些打印信息,进一步看看生成器的工作流程
迭代一步
def my_range(n):
    print('开始迭代...')
    print('-' * 10)

    i = 0
    while i < n:
        print('迭代中...')

        yield i

        print('-' * 10)

        i += 1

    print('迭代结束')


gen_obj = my_range(2)

print(next(gen_obj))
开始迭代...
----------
迭代中...
0
  • 通过结果可以看到:
    • 当调用生成器函数的时候,函数只是返回了一个生成器对象,并没有运行
    • 当第一次调用next()方法的时候,生成器函数才开始运行,运行到yield语句处停止,并将i作为返回值返回
迭代两步
def my_range(n):
    print('开始迭代...')
    print('-' * 10)

    i = 0
    while i < n:
        print('迭代中...')

        yield i

        print('-' * 10)

        i += 1

    print('迭代结束')


gen_obj = my_range(2)

print(next(gen_obj))
print(next(gen_obj))
开始迭代...
----------
迭代中...
0
----------
迭代中...
1
  • 通过结果可以看到:
    • 当第二次调用next()方法的时候,生成器函数从上一次停止的地方,也就是yield语句处继续运行,直到再次运行到yield语句处停止, 并将i作为返回值返回
迭代三步
def my_range(n):
    print('开始迭代...')
    print('-' * 10)

    i = 0
    while i < n:
        print('迭代中...')

        yield i

        print('-' * 10)

        i += 1

    print('迭代结束')


gen_obj = my_range(2)

print(next(gen_obj))
print(next(gen_obj))
print(next(gen_obj))
开始迭代...
----------
迭代中...
0
----------
迭代中...
1
----------
迭代结束
Traceback (most recent call last):
  File "C:/Users/FOLLOW_MY_HEART/Desktop/Python Basics/【Python基础】生成器/test.py", line 22, in <module>
    print(next(gen_obj))
StopIteration
  • 通过结果可以看到:
    • 第三次迭代后,while循环结束,打印“迭代结束”
    • 之后产生StopIteration异常,表示无法继续迭代
  • 为了避免产生异常,可以使用异常处理
def my_range(n):
    print('开始迭代...')
    print('-' * 10)

    i = 0
    while i < n:
        print('迭代中...')

        yield i

        print('-' * 10)

        i += 1

    print('迭代结束')


gen_obj = my_range(2)

while True:
    try:
        print(next(gen_obj))
    except Exception as e:
        break
开始迭代...
----------
迭代中...
0
----------
迭代中...
1
----------
迭代结束

生成器表达式

  • 在介绍生成器表达式之前,先看看我们已经比较熟悉的列表推导式
number_list = [i for i in range(10) if i % 2]

print(number_list)
[1, 3, 5, 7, 9]
  • 上述列表推导式返回一个包含 1 1 1 10 10 10之间所有奇数的列表
  • 当序列很长,而每次只需要获取一个元素时,应当考虑生成器而不是列表
  • 生成器表达式的语法和列表推导式一样,只不过生成器表达式是被()括起来的
gen_obj = (i for i in range(10) if i % 2)

print(gen_obj)
<generator object <genexpr> at 0x000001D6DC1E04A0>
  • 生成器表达式并不是返回一个列表,而是返回一个生成器
  • 使用生成器时,每迭代一次会产生(yield)出一个值来,实现了惰性加载(懒加载),只有在被迭代时才被赋值,并覆盖上一个值,所以在序列较长的情况下,使用生成器会优化内存

send()方法和close()方法

  • 生成器中有两个重要的方法:send()方法和close()方法
send()方法
  • Python 2.5中,yield语句变成了yield表达式,也就是说yield可以有一个值,而这个值就是send()方法的参数,通过调用send()方法将值传递给yield,而send()的返回值为yield返回的值,所以send(None)next()是等效的
  • 注意,调用send()传入非None值前,生成器必须处于挂起状态,否则将抛出异常,也就是说,第一次运行生成器时,只能使用next()send(None),因为没有yield来接收这个值
def my_range(n):
    i = 0
    while i < n:
        val = yield i

        print(f'val 的值为 {val}')

        i += 1


gen_obj = my_range(3)

print(gen_obj.send(None))

print(gen_obj.send('hello'))
print(gen_obj.send('world'))
0
val 的值为 hello
1
val 的值为 world
2
close()方法
  • close()方法用于关闭生成器,对关闭后的生成器再次调用next()send()将抛出StopIteration异常
def my_range(n):
    i = 0
    while i < n:
        val = yield i

        print(f'val 的值为 {val}')

        i += 1


gen_obj = my_range(3)

print(gen_obj.send(None))

print(gen_obj.send('hello'))

gen_obj.close()

print(gen_obj.send('world'))
0
val 的值为 hello
1
Traceback (most recent call last):
  File "C:/Users/FOLLOW_MY_HEART/Desktop/Python Basics/【Python基础】生成器/test.py", line 19, in <module>
    print(my_range.send('world'))
StopIteration

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

丷从心

感谢支持!

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

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

打赏作者

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

抵扣说明:

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

余额充值