按, 上篇介绍了generator相关的概念. 这篇来探究一下generator相关的操作方法. 以下都是使用Python 2.7.10.
根据官方文档 generator 通过 yield 来实现 next()
方法.
> A function which returns a generator iterator. It looks like a normal
> function except that it contains yield expressions for producing a
> series of values usable in a for-loop or that can be retrieved one at
> a time with the next() function.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
>>> def fib():
... a, b = 0, 1
... while True:
... yield b
... a, b = b, a + b
...
>>> fib
<function fib at 0x10d930c80>
>>> f = fib()
>>> f
<generator object fib at 0x10d92faa0>
>>> next(f)
1
>>> next(f)
1
>>> next(f)
2
>>> next(f)
3
>>> next(f)
5
|
以上, 我们可以看到yield用法. 每次执行到yield, 都会记住执行状态的局部变量以及表达式。再下次恢复时, 即next(), 它会从记住的状态继续执行.
generator的另一种调用方法是通过send(value)来实现的。我们来看一个例子:
1
2
3
4
5
6
7
8
9
10
|
>>> def gen():
... while True:
... value = yield
... print(value)
...
>>> g = gen()
>>> g.send("ek")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can't send non-None value to a just-started generator
|
1
2
3
4
5
|
>>> next(g)
>>> g.send("ek")
ek
>>> next(g)
None
|
以上, 展示了如何讲value传入yield当前表达式. 具体来说, 使用send(value)时, generator停在yield的语句. 传入的值被复制到value, 然后print函数打印value, 经过循环遇到yield时, 暂停. 需要注意的是在没有执行next()前, generator状态并没有停在yield状态,也就无法传入值.
1
2
3
4
|
>>> g = gen()
>>> g.send(None)
>>> g.send("ek")
ek
|
以上, 除了next(), 可以让generator到达yield, send(None)作用是一样的.
让我们乘热打铁, 看一个稍微复杂的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
>>> def gen(value = None):
... while True:
... value = (yield value)
... print("The value is", value)
... if value:
... value += 1
...
>>> g = gen(1)
>>> next(g)
1
>>> g.send(2)
('The value is', 2)
3
>>> g.send(10)
('The value is', 10)
11
>>> next(g)
('The value is', None)
|
在这个例子里, value = (yield value) 这个形式看起来很复杂. 但两个value 的含义并不相同. 具体来看, 执行next()时, generator执行到yield value表达式, 保存上下文环境暂停返回当前值1. 再执行send(value)时, 从value = yield开始, 打印传入的值 2, 再次遇到yield value暂停返回当前值 3 (上个循环时 加上 1). send(10)是一样的. 在看最后一个next(), 这里需要记住的是调用next()表达式的值时, yield的值总是为None. 因此, 在这里返回None.
接下来, 我们来实现一下Python 内建函数 range() 实现方法 (复刻版, 非真实实现):
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
26
27
28
29
30
|
>>> def my_range(start, stop, step = 1):
... if stop <= start:
... raise RuntimeError("Start must be smaller than stop")
... i = start
... while i < stop:
... yield i
... i += step
...
>>> try:
... for k in my_range(10, 50, 3):
... print(k)
... except RuntimeError as ex:
... print(ex)
... except:
... print("Unknown error occurred")
...
10
13
16
19
22
25
28
31
34
37
40
43
46
49
|
最后, generator 的另外两个方法分别是 throw(type[, value[, traceback]]) 和 close(). 前者用于抛出 type 异常, 后者用于关闭generator.
我们直接看下Python文档的例子:
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.
|