说说生成器
在Python这门语言中,生成器毫无疑问是最有用的特性之一。与此同时,也是使用的最不广泛的Python特性之一。究其原因,主要是因为,在其他主流语言里面没有生成器的概念。正是由于生成器是一个“新”的东西,所以,它一方面没有引起广大工程师的重视,另一方面,也增加了工程师的学习成本,最终导致大家错过了Python中如此有用的一个特性。
我的这篇文章,希望通过简单易懂的方式,深入浅出地介绍Python的生成器,以改变“如此有用的特性却使用极不广泛”的现象。本文的组织如下:在第1章,我们简单地介绍了Python中的迭代器协议;在本文第2章,将会详细介绍生成器的概念和语法;在第3章,将会给出一个有用的例子,说明使用生成器的好处;在本文最后,简单的讨论了使用生成器的注意事项。
1. 迭代器协议由于生成器自动实现了迭代器协议,而迭代器协议对很多人来说,也是一个较为抽象的概念。所以,为了更好的理解生成器,我们需要简单的回顾一下迭代器协议的概念。
迭代器协议是指:对象需要提供next方法,它要么返回迭代中的下一项,要么就引起一个StopIteration异常,以终止迭代 可迭代对象就是:实现了迭代器协议的对象 协议是一种约定,可迭代对象实现迭代器协议,Python的内置工具(如for循环,sum,min,max函数等)使用迭代器协议访问对象。举个例子:在所有语言中,我们都可以使用for循环来遍历数组,Python的list底层实现是一个数组,所以,我们可以使用for循环来遍历list。如下所示:
>>> for n in [1, 2, 3, 4]: ... print n但是,对Python稍微熟悉一点的朋友应该知道,Python的for循环不但可以用来遍历list,还可以用来遍历文件对象,如下所示:
>>> with open(‘/etc/passwd’) as f: # 文件对象提供迭代器协议 ... for line in f: # for循环使用迭代器协议访问文件 ... print line ...为什么在Python中,文件还可以使用for循环进行遍历呢?这是因为,在Python中,文件对象实现了迭代器协议,for循环并不知道它遍历的是一个文件对象,它只管使用迭代器协议访问对象即可。正是由于Python的文件对象实现了迭代器协议,我们才得以使用如此方便的方式访问文件,如下所示:
>>> f = open('/etc/passwd') >>> dir(f) ['__class__', '__enter__', '__exit__', '__iter__', '__new__', 'writelines', '...' 2. 生成器Python使用生成器对延迟操作提供了支持。所谓延迟操作,是指在需要的时候才产生结果,而不是立即产生结果。这也是生成器的主要好处。
Python有两种不同的方式提供生成器:
生成器函数:常规函数定义,但是,使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行生成器表达式:类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表2.1 生成器函数我们来看一个例子,使用生成器返回自然数的平方(注意返回的是多个值):
def gensquares(N): for i in range(N): yield i ** 2 for item in gensquares(5): print item,使用普通函数:
def gensquares(N): res = [] for i in range(N): res.append(i*i) return res for item in gensquares(5): print item,可以看到,使用生成器函数代码量更少。
2.2 生成器表达式使用列表推导,将会一次产生所有结果:
>>> squares = [x**2 for x in range(5)] >>> squares [0, 1, 4, 9, 16]将列表推导的中括号,替换成圆括号,就是一个生成器表达式:
>>> squares = (x**2 for x in range(5)) >>> squares <generator object at 0x00B2EC88> >>> next(squares) 0 >>> next(squares) 1 >>> next(squares) 4 >>> list(squares) [9, 16]Python不但使用迭代器协议,让for循环变得更加通用。大部分内置函数,也是使用迭代器协议访问对象的。例如, sum函数是Python的内置函数,该函数使用迭代器协议访问对象,而生成器实现了迭代器协议,所以,我们可以直接这样计算一系列值的和:
>>> sum(x ** 2 for x in xrange(4))而不用多此一举的先构造一个列表:
>>> sum([x ** 2 for x in xrange(4)]) 2.3 再看生成器前面已经对生成器有了感性的认识,我们以生成器函数为例,再来深入探讨一下Python的生成器:
语法上和函数类似:生成器函数和常规函数几乎是一样的。它们都是使用def语句进行定义,差别在于,生成器使用yield语句返回一个值,而常规函数使用return语句返回一个值自动实现迭代器协议:对于生成器,Python会自动实现迭代器协议,以便应用到迭代背景中(如for循环,sum函数)。由于生成器自动实现了迭代器协议,所以,我们可以调用它的next方法,并且,在没有值可以返回的时候,生成器自动产生StopIteration异常状态挂起:生成器使用yield语句返回一个值。yield语句挂起该生成器函数的状态,保留足够的信息,以便之后从它离开的地方继续执行3. 示例我们再来看两个生成器的例子,以便大家更好的理解生成器的作用。
首先,生成器的好处是延迟计算,一次返回一个结果。也就是说,它不会一次生成所有的结果,这对于大数据量处理,将会非常有用。
大家可以在自己电脑上试试下面两个表达式,并且观察内存占用情况。对于前一个表达式,我在自己的电脑上进行测试,还没有看到最终结果电脑就已经卡死,对于后一个表达式,几乎没有什么内存占用。
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使用生成器的情况:
def index_words(text): if text: yield 0 for index, letter in enumerate(text, 1): if letter == ' ': yield index这里,至少有两个充分的理由说明 ,使用生成器比不使用生成器代码更加清晰:
使用生成器以后,代码行数更少。大家要记住,如果想把代码写的Pythonic,在保证代码可读性的前提下,代码行数越少越好不使用生成器的时候,对于每次结果,我们首先看到的是result.append(index),其次,才是index。也就是说,我们每次看到的是一个列表的append操作,只是append的是我们想要的结果。使用生成器的时候,直接yield index,少了列表append操作的干扰,我们一眼就能够看出,代码是要返回index。这个例子充分说明了,合理使用生成器,能够有效提高代码可读性。只要大家完全接受了生成器的概念,理解了yield语句和return语句一样,也是返回一个值。那么,就能够理解为什么使用生成器比不使用生成器要好,能够理解使用生成器真的可以让代码变得清晰易懂。
4. 使用生成器的注意事项相信通过这篇文章,大家已经能够理解生成器的作用和好处。但是,还没有结束,使用生成器,也有一点注意事项。
我们直接来看例子,假设文件中保存了每个省份的人口总数,现在,需要求每个省份的人口占全国总人口的比例。显然,我们需要先求出全国的总人口,然后在遍历每个省份的人口,用每个省的人口数除以总人口数,就得到了每个省份的人口占全国人口的比例。
如下所示:
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) #print all_population for population in gen: print population / all_population执行上面这段代码,将不会有任何输出,这是因为,生成器只能遍历一次。在我们执行sum语句的时候,就遍历了我们的生成器,当我们再次遍历我们的生成器的时候,将不会有任何记录。所以,上面的代码不会有任何输出。
因此,生成器的唯一注意事项就是:生成器只能遍历一次。
5. 总结本文深入浅出地介绍了Python中,一个容易被大家忽略的重要特性,即Python的生成器。为了讲解生成器,本文先介绍了迭代器协议,然后介绍了生成器函数和生成器表达式,并通过示例演示了生成器的优点和注意事项。在实际工作中,充分利用Python生成器,不但能够减少内存使用,还能够提高代码可读性。掌握生成器也是Python高手的标配。希望本文能够帮助大家理解Python的生成器。
对我这篇文章感兴趣的同学,也可以看看我之前的回答:怎么样才算是精通 Python?
哈哈,别用了 序列号就用 33333好了,哈哈
不用CD -Key
目前我们中国的情人节七夕就要到来了,单身狗这下又凄惨了,好多人在朋友圈已经开始出卖自己,出卖灵魂了。哈哈哈!!!目前网上正在流行这个七夕出租自己生成器怎么玩的呢?新云小编就来给大家说说。
七夕出租自己说说七夕单身男人出租 (限女性年龄16-26)
统一价格如下:
陪逛街:5/小时 3小时起10元/小时 (鞋底要报销)
陪吃饭:1元/小时 3小时起5元/小时 (雇主请客制)
陪看电影:5元/小时 3场以上8折优惠 (禁止看鬼片)
陪去聚会:5元/小时 3小时起8元/小时 (危险地方不去)
拉手:1元/次 5元/包天
拥抱:2元/次 2元/包天
接吻:3元/次 10元/包天
陪过夜:我倒贴50元
限量
七夕出租自己生成器怎么玩1、首先,打开装逼神器APP主页,点击箭头所指的右上角的搜索框位置!
2、然后输入出租,点击搜索!出现情人节出租,点进去。
3、然后,在情人节出租的界面,上传你的图片,输入姓名和微信号!点击一键生成!
4、然后,你的情人节出租自己的图片就生成好啦!
在QQ空间将鼠标移到上面导航条的“我的主页”下拉菜单里点日志,再点生活记录。 这里可看到上传的图片和发表的说说记录,可一键发表成日志,如果日志仅自己可见,修改下日志访问权限即可。 而更早期的说说要想生成日志,只有逐条手动添加编辑了的。
.
那你就直接在说说那里点击生成日志就可以了,这样的话就是你会形成一个日志,记录你每天的心情的,就相当于你交的时候写的那个一本日志,他给你整合在一起。