您的位置:首页 > 编程语言 > Python开发

Python的迭代器和生成器

2017-04-10 21:34 393 查看
先说迭代器,对于
string
list
dict
tuple
等这类容器对象,使用
for
循环遍历是很方便的。在后台
for
语句对容器对象调用
iter()
函数,
iter()
是python的内置函数。
iter()
会返回一个定义了
next()
方法的迭代器对象,它在容器中逐个访问容器内元素,
next()
也是python的内置函数。在没有后续元素时,
next()
会抛出一个
StopIteration
异常,通知
for
语句循环结束。比如:

>>> s = 'abc'
>>> it = iter(s)
>>> it
<str_iterator object at 0x7f71fefe9d68>
>>> next(it)
'a'
>>> next(it)
'b'
>>> next(it)
'c'
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

上面说的都是python自带的容器对象,它们都实现了相应的迭代器方法,那如果是自定义类需要遍历怎么办?方法很简单,对这个类AClass,实现一个
__iter__(self)
方法,使其返回一个带有
__next__(self)
方法的对象就可以了。如果你在AClass刚好也定义了
__next__(self)
方法(一般使用迭代器都会定义),那在
__iter__
里只要返回
self
就可以。废话少说,先上代码:

class Fib(object):
def __init__(self, max):
super(Fib, self).__init__()
self.max = max

def __iter__(self):
self.a = 0
self.b = 1
return self

def __next__(self):
fib = self.a
if fib > self.max:
raise StopIteration
self.a, self.b = self.b, self.a + self.b
return fib

def main():
fib = Fib(100)
for i in fib:
print(i)

if __name__ == '__main__':
main()

简单讲下代码会干什么,定义了一个Fib类,用于生成fibonacci序列。用for遍历时会逐个打印生成的fibonacci数,max是生成的fibonacci序列中数字大小的上限。

在类的实现中,定义了一个
__iter__(self)
方法,这个方法是在遍历时被
iter()
调用,返回一个迭代器。因为在遍历的时候,是直接调用的python内置函数
iter()
,由
iter()
通过调用
__iter__(self)
获得对象的迭代器。有了迭代器,就可以逐个遍历元素了。而逐个遍历的时候,也是使用内置的
next()
函数通过调用对象的
__next__(self)
方法对迭代器对象进行遍历。所以要实现
__iter__(self)
__next__(self)
。而且因为实现了
__next__(self)
,所以在实现
__iter__(self)
的时候,直接返回self就可以。

为了更好理解,我再简单重复下上面说的那一段:在循环遍历自定义容器对象时,会使用python内置函数
iter()
调用遍历对象的
__iter__(self)
获得一个迭代器,之后再循环对这个迭代器使用
next()
调用迭代器对象的
__next__(self)
__iter__
只会被调用一次,而
__next__
会被调用 n 次。

下面说生成器。

生成器(Generator)是创建迭代器的简单而强大的工具。它们写起来就像是正规的函数,只是在需要返回数据的时候使用
yield
语句。每次
next()
被调用时,生成器会返回它脱离的位置(它记忆语句最后一次执行的位置和所有的数据值)。以下示例演示了生成器可以很简单的创建出来:

>>> def reverse(data):
...     for index in range(len(data)-1, -1, -1):
...         yield data[index]
...
>>> for char in reverse('hello'):
...     print(char)
...
o
l
l
e
h

关于迭代器和生成器的区别,生成器能做到迭代器能做的所有事,而且因为自动创建了
__iter__()
next()
方法,生成器显得特别简洁,而且生成器也是高效的。除了创建和保存程序状态的自动方法,当发生器终结时,还会自动抛出
StopIteration
异常。一个带有
yield
的函数就是一个 生成器,它和普通函数不同,生成一个 generator 看起来像函数调用,但不会执行任何函数代码,直到对其调用
next()
(在
for 循环中会自动调用
next()
)才开始执行。虽然执行流程仍按函数的流程执行,但每执行到一个
yield
语句就会中断,并返回一个迭代值,下次执行时从
yield
的下一个语句继续执行。看起来就好像一个函数在正常执行的过程中被
yield
中断了数次,每次中断都会通过
yield
返回当前的迭代值(
yield
暂停一个函数,
next()
从其暂停处恢复其运行)。

另外对于生成器,python还提供了一个生成器表达式:类似与一个
yield
值的匿名函数。表达式本身看起来像列表推到, 但不是用方括号而是用圆括号包围起来:

>>> unique_characters = {'E', 'D', 'M', 'O', 'N', 'S', 'R', 'Y'}
>>> gen = (ord(c) for c in unique_characters)
>>> gen
<generator object <genexpr> at 0x7f2be4668678>
>>> for i in gen:
...     print(i)
...
69
79
83
77
82
78
89
68
>>>

如果需要,可以将生成器表达式传给
tuple
list
或是
set
来迭代所有的值并且返回元组、列表或是集合。在这种情况下,不需要一对额外的括号 ———— 直接将生成器表达式
ord(c) for c in unique_characters
传给
tuple()
等函数就可以了, Python 会推断出它是一个生成器表达式。

最后,为什么要使用生成器?因为效率。使用生成器表达式取代列表解析可以同时节省 cpu 和 内存(ram)。如果你构造一个列表的目的仅仅是传递给别的函数,(比如 传递给
tuple()
或者
set())
, 那就用生成器表达式替代吧!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: