【Python基础】生成器

发布时间:2023年12月18日

什么是生成器

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

生成器示例

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

        i += 1


my_range = my_range(3)

print(my_range)

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

        i += 1


my_range = my_range(3)

print(dir(my_range))

print('__iter__' in dir(my_range))
print('__next__' in dir(my_range))
['__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('开始迭代...')

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

        yield i

        print('-' * 10)

        i += 1

        print('迭代结束')


gen_obj = my_range(3)

print(next(gen_obj))
print(next(gen_obj))
print(next(gen_obj))
开始迭代...
迭代中...
0
----------
迭代结束
迭代中...
1
----------
迭代结束
迭代中...
2

1

  • 通过结果可以看到:
    • 当调用生成器函数的时候,函数只是返回了一个生成器对象,并没有运行
    • 当第一次调用next()方法的时候,生成器函数才开始运行,运行到yield语句处停止,并将i作为返回值返回,执行过程为图中的①
    • 当继续调用next()方法的时候,生成器函数从上一次停止的地方,也就是yield语句处继续运行,直到再次运行到yield语句处停止, 并将i作为返回值返回,执行过程为图中的②③
    • 如果下一次迭代不能运行到yield语句,就抛出StopIteration异常,生成器在当前yield语句处停止,不再执行②③过程

生成器表达式

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

print(nums)
[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:
        var = yield i

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

        i += 1


my_range = my_range(3)

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

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

        i += 1


my_range = my_range(3)

print(my_range.send(None))
print(my_range.send('hello'))

my_range.close()

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

文章来源:https://blog.csdn.net/from__2023_11_28/article/details/135063088
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。