设为首页 收藏本站
查看: 784|回复: 0

[经验分享] python中的generator解读

[复制链接]

尚未签到

发表于 2017-4-29 11:28:49 | 显示全部楼层 |阅读模式
简介
  在以前一篇讲述iterator的文章里,我提到过通过一种循环遍历的方式去访问一个python对象需要具备的特性。在那里,我们要实现一个iterator的时候,要定义一个可以访问的迭代器__iter__,同时也需要定义一个next()方法用来获取后面的一个元素。这是一种遍历集合元素的方式。这里主要依赖的是我们在一个集合元素已经取得的情况下。使用iterator的时候,我又碰到一个看起来很近似的东西,generator。generator有什么作用呢?为什么要专门折腾一个generator出来?我们一一讨论过来。

Generator和Iterator的比较
  我们来看一段很简单的代码:

def numlist(n):
...     while(n < 10):
...             yield n
...             n += 1

  这里有一个看似有点怪异的语法,yield n。如果我们要使用这个方法,该怎么用呢?

>>> for i in numlist(0):
...     print i,
...
0 1 2 3 4 5 6 7 8 9

  从用法来看,这里好像是numlist()作为集合来遍历。如果我们了解iterator的话,我们完全也可以用iterator实现一个来达到同样的目的啊,比如说我们实现一个有同样输出的iterator如下:

class numlist:
def __init__(self, start):
self.count = start
def __iter__(self):
return self
def next(self):
if self.count >= 10:
raise StopIteration
r = self.count
self.count += 1
return r

  如果我们用如下的代码来遍历的话,也会得到如下的结果:

>>> for i in numlist(0):
...     print i,
...
0 1 2 3 4 5 6 7 8 9

  从表面上看起来,他们两者都返回类似集合的东西,好像没什么区别。我们深入的分析一下看看。我们先来看看前面定义的generator部分:

>>> from genlist import numlist
>>> x = numlist(0)
>>> x
<generator object numlist at 0x7f217108a780>
  输出的结果显示numlist方法返回的结果是一个generator。
  而如果我们引用那个iterator的实现,则结果如下:

>>> from countup import numlist
>>> x = numlist(0)
>>> x
<countup.numlist instance at 0x7f17e42a1440>

  这里显示的x是一个numlist的实例对象。而如果我们进一步去深究的,这个x是什么详细的定义呢?我们在python命令行里输入help(x):

class numlist
|  Methods defined here:
|  
|  __init__(self, start)
|  
|  __iter__(self)
|  
|  next(self)

  从结构上来说,这里就是我们前面讨论过的典型的iterator。我们再来看generator那部分的:

numlist = class generator(object)
|  Methods defined here:
|  
|  __getattribute__(...)
|      x.__getattribute__('name') <==> x.name
|  
|  __iter__(...)
|      x.__iter__() <==> iter(x)
|  
|  __repr__(...)
|      x.__repr__() <==> repr(x)
|  
|  close(...)
|      close(arg) -> raise GeneratorExit inside generator.
|  
|  next(...)
|      x.next() -> the next value, or raise StopIteration
|  
|  send(...)
|      send(arg) -> send 'arg' into generator,
|      return next yielded value or raise StopIteration.
|  
|  throw(...)
|      throw(typ[,val[,tb]]) -> raise exception in generator,
|      return next yielded value or raise StopIteration.
|  
|  ----------------------------------------------------------------------
|  Data descriptors defined here:
|  
|  gi_code
|  
|  gi_frame
|  
|  gi_running

  和前面的iterator不一样,这里有一个生成的generator类,然后在类里多了close, send等几个方法。看到这里的时候,我们发现generator里完全有和iterator一样的功能,比如通过next()方法来遍历他们。但是我们在前面定义的时候更加简练一些。

Generator和list的比较
  在前面这一部分我们比对了generator和iterator的结构形式。发现generator的功能似乎涵盖了iterator。除了我们前面实现代码的一种定义方式,我们还有一种实现generator的形式,它看起来和list comprehension很相似。我们看如下的代码:

>>> a = [1, 2, 3, 4]
>>> b = (2*x for x in a)
>>> b
<generator object <genexpr> at 0x16e5a00>
>>> for i in b: print i,
...
2 4 6 8

  在前面的代码里,如果我们将b定义成[2*x for x in a],得到的结果则不一样了:

>>> c = [2*x for x in a]
>>> c
[2, 4, 6, 8]
>>> type(c)
<type 'list'>

  仅仅是一个括号的区别,返回的结果就完全不同。虽然我们也可以用generator来作为遍历的结果。那么generator和list遍历的结果有什么不一样呢?
  在前面的代码里,我们执行第一次循环generator的时候,输出一组数字,可是这个generator只能使用一次,而list却是可以使用无数次的:

>>> for i in b: print i,
...
2 4 6 8
>>> for i in b: print i,
...
>>> for i in a: print i,
...
1 2 3 4
>>> for i in a: print i,
...
1 2 3 4

  看来这个generator就像个一次性消费品,定义起来简单,但是一次就用完了。这么看来,generator还比较麻烦,还不如list呢。其实generator还有一个优点,就是我们一般遍历数据的时候,都是需要已经构造好一个集合了,然后再去遍历它们。这样如果在数据集合比较大的时候就变得不可行了。而generator并不预先构造一个集合。每次我们循环遍历的时候,循环一次就取一个数据。因为这么个特性,它不需要将所有的集合数据都保存下来。在一些大规模数据的处理的情况下,它显得更加高效率。

Generator的作用和思想
  在前面的描述中generator是一个可以无限取数据的流水线,对它的使用就像是操作一个集合一样,但是一旦启用了之后它从读取数据到所有数据操作完毕,它只能用一次。在一些场景中,我们可能需要对一些数据做多个步骤的处理。如果我们从一开始对数据进行操作的时候就使用generator的话,会发现后面都必须要利用它来做进一步的组合。我们来看一个示例。
  假设我们有一个log文件,那里记录了web服务器数据访问的信息,它里面保存的信息是如下格式的:

81.107.39.38 - ... "GET /ply/ply.html HTTP/1.1" 200 97238

  在一行最末尾的地方记录的是一次访问操作的时候传输的数据量大小,单位为字节。最后一位可以为一个数字或者为一个-字符。如果我们需要统计里面所有请求的数据量,那么该怎么来实现呢?下面是一种是我们想到的传统实现方法:

wwwlog = open("access-log")
total = 0
for line in wwwlog:
bytestr = line.rsplit(None,1)[1]
if bytestr != '-':
total += int(bytestr)
print "Total", total

  这里的代码思路比较简单,首先我们打开这个log文件,然后读取文件的每一行,再将取到的没一行里的数据量相加,最后得到总数据传数量。
  如果我们结合前面generator的用法来看,这里还有一种写法:

wwwlog = open("access-log")
bytecolumn = (line.rsplit(None,1)[1] for line in wwwlog)
bytes = (int(x) for x in bytecolumn if x != '-')
print "Total", sum(bytes)

  我们取每一行里的数据量部分是返回一个generator,然后每次针对这个部分解析数值也是返回一个generator,最后再通过一个sum()方法来统计所有的和。在后面这种写法里,我们更多的像是在声明说要取哪些数据,然后每一步是在原来generator的基础上再套generator。有点流水线套流水线的味道。和前面的比起来,这种写法更加简单和直观。代码行数都更少一些。

总结
  generator的定义可以通过yield n或者类似于list comprehension的方法来构造。它本身不构造数据列表,因此可以处理理论上无穷的数据而不会导致机器的存储资源耗尽。generator类似于一个车间生产的流水线,每次需要用产品的时候才临时从那里取一个,然后这个流水线就停在那里等待下一次取操作。我们可以在实际应用中将多个generator串在一起来用,这一点和unix设计思想里pipeline的思想居然是惊人的一致。另外,如果我们使用generator写代码来表述一些处理的过程时,体现出来的更多是一种声明式的写法,这和函数式编程的思想居然有如此紧密的联系。关于这些关系值得以后文章里进一步的探讨。

参考材料
  http://www.dabeaz.com/generators/

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-370732-1-1.html 上篇帖子: python queue 学习笔记 下篇帖子: Python正则表达式(三)
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表