没脚的雀

python的generator理解(1)--实现迭代器协议


python中的生成器主要经历三个发展阶段:

  1. 作为实现迭代器协议的一种方式而出现(yield语句)
  2. 增添方法,使生成器能够支持协程(增加close,send,throw方法,调整yield语句为yield表达式)
  3. 为了重构方便增加了yield from 语法

在这篇博文中,主要介绍用于第一点的生成器


实现迭代器协议的方法

假设你有一个文件,你需要为文件的每一行添加两边添加上<p> 标签

类方法实现迭代器协议

1
2
3
4
5
6
7
8
9
10
11
12
13
class addPC(object):
def __init__(self, filename):
self.file = open(filename)
def __iter__(self):
return self
def next(self):
line = self.file.readline()
if line == "":
self.file.close()
raise StopIteration
line = line.strip()
line = line.join(("<p>","</p>"))
return line

上面这个类实现了迭代器协议,可以在for里面使用

1
2
3
for line in addPC("ok.txt"):
print line
## output : <p>asdfdsfas</p>

使用yield语法实现迭代器

1
2
3
4
5
6
7
8
def addP(filename):
f = open(filename)
for line in f:
line = line.strip()
line = line.join(("<p>", "</p>"))
yield line
f.close()
# 定义一个generator function

来看看一个generator object 拥有的行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
In [19]: gen_obj = addP("ok.txt")   # addP("ok.txt") 返回的是一个generator object

In [20]: gen_iter = iter(gen_obj) # 一个generator object 实现了迭代器协议中的 __iter__方法

In [21]: gen_iter is gen_obj # __iter__方法返回的是 generator object 自身,
Out[21]: True # 所以generator obje 是一个迭代器

In [22]: next(gen_iter) # generator object 的next方法,将会运行值yield语句处
Out[22]: '<p>asdfdsfas</p>' # 将 yield 语句中的参数作为next 方法的返回值

In [23]: next(gen_iter) # 当gen_obj 没有数据可以输出的时候,将会抛出一个StopIteration(这也是迭代器协议的内容)
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-23-263eea1e8e67> in <module>()
----> 1 next(gen_iter)

StopIteration:

其中,像addP这种包含yield的函数称为generator function,调用generator function得到的返回值是一个generator object;而一个generator object实现了迭代器协议(__iter__方法和next方法),所以一个generator object可以出现在一切需要可迭代对象的地方, 例如:for语句中,next、iter函数的参数,等等。

通过使用yield,我们可以将不同的next方法调用之间的全局变量的操作变成对局部变量的操作

yield的限制

与return 共存

需要说明的是,在pep325之后,不存在这条限制

允许不带参数的return语句存在generator function中,代表这generator将会抛出一个StopIteration异常

不可在try/finally结构的try分句中使用

需要说明的是,在pep325之后,不存在这条限制

在当时,pep255给出的理由是,由于try/finally结构用于确保占用资源(fd之类的系统资源)的释放,如果在try分句中出现了yield, 无法保证finally语句中的代码块被执行

小结

  • 生成器最初是实现迭代器的一个简化的方法
  • 包含了yield语句的函数就是generator function, generator function的调用返回值是一个generator object
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
31
32
33
34
35
36
37
38
39
class TreeNode:
def __init__(self, val, left = None, right = None, traver_way = "inOrder"):
self.val = val
self.right = right
self.left = left
self.traver_way = traver_way

def __iter__(self):
gen_iterator = getattr(self, self.traver_way)
if gen_iterator is None:
print("traver_way should in ['inOrder', 'preOrder', 'houOrder']")
return gen_iterator(self)

@staticmethod
def inOrder(node):
if node is None:
raise StopIteration
else:
yield from TreeNode.inOrder(node.left)
yield node.val
yield from TreeNode.inOrder(node.right)

@staticmethod
def preOrder(node):
if node is None:
raise StopIteration
else:
yield node.val
yield from TreeNode.preOrder(node.left)
yield from TreeNode.preOrder(node.right)

@staticmethod
def houOrder(node):
if node is None:
raise StopIteration
else:
yield from TreeNode.houOrder(node.left)
yield from TreeNode.houOrder(node.right)
yield node.val
大佬给口饭吃咧