Python中的生成器和yield语句


yield表达式在Python的使用中非常广泛,最近在做爬虫项目的时候经常有用到。yield在函数中的功能类似于return,不同的是yield每次返回结果之后函数并没有退出,而是每次遇到yield关键字后返回相应结果,并保留函数当前的运行状态,等待下一次的调用。如果一个函数需要多次循环执行一个动作,并且每次执行的结果都是需要的,这种场景很适合使用yield实现。


在Python文档中,对于yield表达式的描述为

yield 表达式仅在定义generator函数时使用,因此只能用在函数定义的函数体中。在函数体中使用 yield 表达式会使该函数成为 generator

当调用 generator 函数时,它会返回一个 iterator,同时又是一个 generator。然后,generator 控制 generator 函数的执行。当一个生成器的方法被调用时执行开始。此时,执行进行到第一个yield表达式,在那里它被再次挂起,将expression_list的值返回给生成器的调用者。挂起,我们的意思是保留所有局部状态,包括局部变量的当前绑定,指令指针,内部计算栈和任何异常处理的状态。当通过调用其中一个生成器的方法来恢复执行时,函数可以像yield表达式只是另一个外部调用一样继续进行。恢复后的yield表达式的值取决于恢复执行的方法。如果使用__next__()(通常通过fornext()内置函数),则结果为None。否则,如果使用send(),则结果将是传递到该方法的值。

其中提到了两个在Python比较重要的类型,generatoriterator,生成器和迭代器,在弄懂生成器之前,先来了解一下迭代器。

迭代器

迭代是Python最强大的功能之一,是访问集合元素的一种方式。

迭代器是一个可以记住遍历的位置的对象。

迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。

迭代器有两个基本的方法:iter()next()

字符串,列表或元组对象都可用于创建迭代器:

1
2
3
4
5
6
>>>list=[1,2,3,4]
>>> it = iter(list) # 创建迭代器对象
>>> print (next(it)) # 输出迭代器的下一个元素
1
>>> print (next(it))
2

关于迭代器,我们只要了解最关键的地方在于它的__iter__()__next__()方法。

__iter__() 方法返回一个特殊的迭代器对象, 这个迭代器对象实现了__next__() 方法并通过 StopIteration 异常标识迭代的完成。

__next__()方法(Python 2 里是 next())会返回下一个迭代器对象。


下面再来回到关于生成器,简单理解迭代器和生成器的关系其实是包含,生成器能做到的事,迭代器也能做到,生成器是一种可以简单有效的创建迭代器的工具。生成器在需要返回数据时使用yield语句,每个yield返回一个generator iterator的函数,记住执行的位置和状态(包括局部变量和等待的 try 语句)。当generator iterator恢复时,即对它调用next()时,它从离开的位置重新开始(与函数不同,函数每次调用从起始开始,并会记住所有的数据值和上次执行的语句)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>>> def echo(value=None):
... print("Execution starts when 'next()' is called for the first time.")
... try:
... while True:
... try:
... value = (yield value)
... except Exception as e:
... value = e
... finally:
... print("Don't forget to clean up when 'close()' is called.")
...
>>> generator = echo(1)
>>> print(next(generator))
Execution starts when 'next()' is called for the first time.
1
>>> print(next(generator))
None
>>> print(generator.send(2))
2
>>> generator.throw(TypeError, "spam")
TypeError('spam',)
>>> generator.close()
Don't forget to clean up when 'close()' is called.

more…

Specification: Try/Except/Finally

As noted earlier, yield is not allowed in the try clause of a try/finally construct. A consequence is that generators should allocate critical resources with great care. There is no restriction on yield otherwise appearing in finally clauses, exceptclauses, or in the try clause of a try/except construct:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
> >>> def f():
> ... try:
> ... yield 1
> ... try:
> ... yield 2
> ... 1/0
> ... yield 3 # never get here
> ... except ZeroDivisionError:
> ... yield 4
> ... yield 5
> ... raise
> ... except:
> ... yield 6
> ... yield 7 # the "raise" above stops this
> ... except:
> ... yield 8
> ... yield 9
> ... try:
> ... x = 12
> ... finally:
> ... yield 10
> ... yield 11
> >>> print list(f())
> [1, 2, 4, 5, 8, 9, 10, 11]
>
-------------本文结束 感谢您的阅读-------------
0%