1.可迭代对象
为了说明可迭代对象,首先我们要知道,迭代的概念。我们先来看一个实例:
ls = [1,2,3,4,5] for i in ls: print(i)
上面的实例非常简单,我们创建了一个列表ls,并且用for语句遍历这个列表的每一个元素。这里列表ls被遍历的这个行为,就称之为迭代。
明白了迭代的概念之后,你就会发现,在Python中,能够被迭代的不只有列表,例如你还可以迭代字符串,元组,文件,字典等等,这些能够被迭代的对象,就称作可迭代对象。
2.生成器
迭代固然是处理大量数据的好方法。但是以列表为例,迭代存在两个问题,第一,如果列表中的元素太多了,将大量占用内存。第二,我们有时候只需要使用一次数据,如果用列表把数据全部保存起来,岂不是有些浪费?Python中的生成器就能很好的解决这两个问题。
2.1.生成器函数
生成器是一种可以简单有效的创建迭代器的工具。它们像常规函数一样撰写,但是在需要返回数据时使用yield语句。每当对它调用next()函数(有关next函数下面会提及),生成器从它上次停止的地方重新开始(它会记住所有的数据值和上次执行的语句)。
上面是我在Python官方文档中找到的定义,下面以实例作为解释:
def print_letter(data): for index in range(len(data)): yield data[index] g = print_letter("hello") for i in g: print(i) 等价于 def print_letter(data): for index in range(len(data)): yield data[index] for i in print_letter("hello"): print(i) 打印结果: h e l l o
上述代码中,def部分定义了一个生成器函数,print_letter("hello")这行代码将返回一个生成器。为了理解生成器(即def定义的部分),我们可以从函数的角度理解,当调用函数时,代码是按顺序结构执行的,生成器与函数的区别在于,函数遇到return返回,而生成器执行到yield时返回yield之后的语句;另外,函数返回时会释放内部定义的变量,而生成器则会保持退出时的状态。以上面代码为例,如果print_letter是函数,那么只返回一次数据(将yield改成return,也就是返回data[0]);但对于生成器,它将在一次调用后接着进入之前的状态,执行代码,每次都返回yield后的语句,直到不再满足条件时停止(对于这里的例子,yield第一次返回data[0],第二次返回data[1],以此类推,当不再满足for遍历条件时就不再返回yield后的语句了)。除了使用for遍历以外,生成器可以不断调用next()函数来实现“遍历”,另外需要指出的是,如果你用next函数遍历完生成器后,程序将会抛出一个StopIteration的异常。
如果使用type函数查看print_letter的类型,可以验证它是生成器(generator)
...type(print_letter) >>>function ...type(print_letter('hello')) >>>generator
2.2.生成器表达式
除了像函数那样定义一个生成器之外,还有一种定义生成器的简单方法。即生成器表达式。
介绍生成器表达式之前我们先介绍一下和它长的很像的列表生成式(List Comprehension),看下面实例:
# 两种方式产生列表[1,4,9,16] ls = [] for i in range(1,5): ls.append(i*i) 等价于 ls = [i**i for i in range(1,5)] # 列表生成式
通过实例可以看出,列表生成式是一种定义列表的简洁方法,实际中也推荐大家使用,这种写法更加pythonic。知道了列表生成式,就很容易得到生成器表达式了。
以上面的代码为例,要把列表生成式修改成生成器表达式,只需要把[]改为(),即
>>>g = (i**i for i in range(1,5)) # 生成器表达式 >>>print(type(g)) <class 'generator'>
可见,构建一个生成器有两种方式,1.生成器函数 2.生成器表达式 。按照实际情况选择。
3.yield关键字
如果你理解了我前面所说的生成器函数运作机制,那么yield关键词你应该也懂了。
再次概括的话就是:生成器内部的代码执行到yield会返回,返回的内容为yield后的表达式。下次再执行生成器的内部代码时将从上次的状态继续开始。通过yield关键字,我们可以很方便的将一个函数修改为生成器。
4.生成器的实际运用
你可能会问,生成器到底能干啥?下面我贴上部分爬取猫眼电影top100的实战代码,希望对你有所启发。
# 提取目标内容并且格式化 def parse_one_page(html): pattern = re.compile('<dd>.*?index.*?>(.*?)</i>.*?<img data-src="(.*?)".*?</a>.*?name"><a.*?>(.*?)</a>.*?star">(.*?)</p>.*?releasetime">(.*?)</p>.*?integer">(.*?)</i>.*?fraction">(.*?)</i>',re.S) items = re.findall(pattern, html) for item in items: yield { # 每次被调用返回yield后面的参数 这里是一个字典(代表一条电影信息) 'index' : item[0], 'image' : item[1], 'title' : item[2].strip(), 'actor' : item[3].strip()[3:] if len(item[3]) > 3 else '', 'time' : item[4].strip()[5:] if len(item[4]) > 5 else '', 'score' : item[5].strip() + item[6].strip() } # 爬取一个网页 def main(offset): url = 'http://maoyan.com/board/4?offset='+str(offset) html = get_one_page(url) for item in parse_one_page(html): # Parse_one_page(html)是一个个可迭代对象 实质上是个生成器 print(item) write_to_file(item)
5.总结
一开始我们介绍了迭代的概念,从迭代的问题引出了生成器的概念,阐述了生成器的运行机制,介绍了两种构建生成器的方式(生成器函数以及生成器表达式),解释了yield关键字的作用,最后我们通过实战代码见识了生成器在实际项目中作用。