对生成器和迭代器的理解(1)

迭代器协议

迭代器协议是指:对象需要提供next方法,它要么返回迭代中的下一项,要么就引起一个StopIteration异常,以终止迭代 。

可迭代对象就是:实现了迭代器协议的对象 。

协议是一种约定,可迭代对象实现迭代器协议。

Python的内置工具(如for循环,sum,min,max函数等)使用迭代器协议访问对象

我们可以使用for循环来遍历list:

for i in [1,2,3,4,5]:
    print(i)

但是,我们知道Python的for循环不仅可以遍历list等,还可以用来遍历文件对象,如下所示:

with open('demo01.txt') as f:
    for line in f:
        print(line)

为什么在Python中,文件还可以使用for循环进行遍历呢?这是因为,在Python中,文件对象实现了迭代器协议,for循环并不知道它遍历的是一个文件对象,它只管使用迭代器协议访问对象即可。正是由于Python的文件对象实现了迭代器协议,我们才得以使用如此方便的方式访问文件,如下所示:
这里写图片描述

生成器

Python使用生成器对延迟操作提供了支持。所谓延迟操作,是指在需要的时候才产生结果,而不是立即产生结果。这也是生成器的主要好处。

Python有两种不同的方式提供生成器:
生成器函数:常规函数定义,但是,使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行。
生成器表达式:类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表

生成器函数

使用生成器返回自然数的平方(注意返回的是多个值):

def gensquares(N):
    for i in range(N):
        yield i**2

for item in gensquares(5):
    print (item)

使用普通函数:

def gensquares(N):
    list = []
    for i in range(N):
        list.append(i**2)
    return list

for item in gensquares(5):
    print (item)

可以看到使用生成器函数代码量更少。

生成器表达式

使用列表推导,将一次产生所有结果:

s = [x*2 for x in range(5)]
print(s)

将列表中的中括号替换成为圆括号,就是一个生成器表达式:

s = (x*2 for x in range(5))
print(s)
print(next(s))
print(next(s))
print(next(s))
print(next(s))
print(next(s))


print(next(s))
s = (x*2 for x in range(5))
print(s)

print(list(s))

Python不但使用迭代器协议,让for循环变得更加通用。大部分内置函数,也是使用迭代器协议访问对象的。例如, sum函数是Python的内置函数,该函数使用迭代器协议访问对象,而生成器实现了迭代器协议,所以,我们可以直接这样计算一系列值的和:

from ipython_genutils.py3compat import xrange

print(sum(x ** 2 for x in xrange(4)))

而不是多此一举先构造一个列表:

print(sum([x ** 2 for x in xrange(4)]))

生成器

语法上和函数类似:生成器函数和常规函数几乎是一样的。它们都是使用def语句进行定义,差别在于,生成器使用yield语句返回一个值,而常规函数使用return语句返回一个值。

自动实现迭代器协议:对于生成器,Python会自动实现迭代器协议,以便应用到迭代背景中(如for循环,sum函数)。由于生成器自动实现了迭代器协议,所以,我们可以调用它的next方法,并且,在没有值可以返回的时候,生成器自动产生StopIteration异常。

状态挂起:生成器使用yield语句返回一个值。yield语句挂起该生成器函数的状态,保留足够的信息,以便之后从它离开的地方继续执行。

生成器的好处是延迟计算,一次返回一个结果,对于大数据量处理,将会非常有用。
对于前一个表达式,我在自己的电脑上进行测试,还没有看到最终结果电脑就已经卡死,对于后一个表达式,,几乎没有什么内存占用。

sum([i for i in xrange(10000000000)])
sum(i for i in xrange(10000000000))

除了延迟计算,生成器还能有效提高代码可读性。例如,现在有一个需求,求一段文字中,每个单词出现的位置。
不使用生成器的情况:

# def index_words(text):
#     result = []
#     if text:
#         result.append(0)
#     for index,letter in enumerate(text,1):
#         if letter == '':
#             result.append(index)
#     return result
# text = 'abcde'
# print(index_words(text))

def index_words(text):
    if text:
        yield 0
    for index,letter in enumerate(text,1):
        if letter == ' ':
            yield index
text = 'abcde'
print(index_words(text))

这里,至少有两个充分的理由说明 ,使用生成器比不使用生成器代码更加清晰:

使用生成器以后,代码行数更少。不使用生成器的时候,对于每次结果,我们首先看到的是result.append(index),其次,才是index。

使用生成器的注意事项

假设文件中保存了每个省份的人口总数,现在,需要求每个省份的人口占全国总人口的比例。
我们需要先求出全国的总人口,然后在遍历每个省份的人口,用每个省的人口数除以总人口数,就得到了每个省份的人口占全国人口的比例。

def get_province_population(filename):
    with open(filename) as f:
        for line in f:
            yield int(line)

gen = get_province_population('data.txt')
all_population = sum(gen)
for population in gen:
    print(population/all_population)

执行上面这段代码,将不会有任何输出,这是因为,生成器只能遍历一次。在我们执行sum语句的时候,就遍历了我们的生成器,当我们再次遍历我们的生成器的时候,将不会有任何记录。所以,上面的代码不会有任何输出。
因此,生成器的唯一注意事项就是:生成器只能遍历一次。

猜你喜欢

转载自blog.csdn.net/enjolras_fuu/article/details/80302084
今日推荐