1. 迭代器

什么是生成器呢,其实生成器的本质就是迭代器;在python中有3中方式来获取生成器(这里主要介绍前面2种)

  • 通过生成器函数获取
  • 通过各种推导式来实现生成器

生成器函数

我们来看一个普通的函数:

  1. In[2]: def func1():
  2. ...: print('aaaa')
  3. ...: return 1111
  4. ...:
  5. In[3]: fun = func1()
  6. aaaa
  7. In[4]: print(fun)
  8. 1111

那么生成器函数跟普通函数有什么不同呢,我们只要把其中的return换成yield关键字参数就是生成器函数了:

  1. In[5]: def func1():
  2. ...: print('aaaa')
  3. ...: yield 1111
  4. ...:
  5. In[6]: fun = func1() # 此时并没有任何打印信息,可以说明函数并没有执行
  6. In[7]: print(fun) # 从输出可以看出这是一个生成器对象
  7. <generator object func1 at 0x0000016F900D6DB0>

从上面的结果来看,我们发现函数func1根本就没有执行,而最后打印的是一个内存地址,这个就是生成器很明显的一个特性:惰性计算,那么我们要怎么执行它呢?我们可以回顾一下迭代器的取值方法:使用迭代器的__next__的方法可以取到迭代器的一个值,那生成器的本质就是迭代器,那我们也可以试下可以这样取值

  1. In[8]: fun.__next__() # 从输出可以看出,yield也和return一样可以有返回值
  2. aaaa # 这里我们就可以看到函数中的aaaa也打印了,表示函数在此处才执行
  3. Out[8]: 1111

我们再来看个例子,观察下生成器是怎么工作的:

  1. In[9]: def func1():
  2. ...: print('aaaa')
  3. ...: yield '我是第一个yield'
  4. ...: print('bbbb')
  5. ...: yield '我是第二个yield'
  6. ...: print('cccc')
  7. ...:
  8. In[10]: gen = func1() # 这里得到的是一个生成器,此处并不会运行函数
  9. ...: print(gen)
  10. <generator object func1 at 0x0000016F900F8BA0>
  11. In[11]: print(gen.__next__()) # 首次执行生成器的__netx__()函数时,开始执行函数,
  12. aaaa # 直到遇到yield时返回,并且yield也可以有返回值
  13. 我是第一个yield
  14. In[12]: print(gen.__next__()) # 再次运行__netx__()函数时,会继续执行函数(从上次yield的位置继续执行)
  15. bbbb
  16. 我是第二个yield
  17. In[13]: print(gen.__next__()) # 再次执行__next__()方法继续执行,此处再往下执行时没有了yield关键字,
  18. cccc # 会抛出StopIteration异常(但时会执行后面的代码)
  19. Traceback (most recent call last):
  20. File "D:\Environment\python-virtualenv\jupyter\lib\site-packages\IPython\core\interactiveshell.py", line 3265, in run_code
  21. exec(code_obj, self.user_global_ns, self.user_ns)
  22. File "<ipython-input-13-9340d28f24b7>", line 1, in <module>
  23. print(gen.__next__())
  24. StopIteration

从上面我们呢可以总结出:

  • yield也可以像return一样也是返回值
  • yield执行完之后会返回到调用者,执行后续的代码,直到再次调用__next__方法,此时生成器函数再从上次停止的位置继续执行
  • 当执行__next__方法后没有yield关键字时,会抛出StopIteration异常,但是会执行yield后面的代码

send方法

接下来我们来看send⽅法, send和__next__()⼀样都可以让⽣成器执⾏到下⼀个yield

  1. In[14]: def eat():
  2. ...: print("aaaa")
  3. ...: a = yield 1111
  4. ...: print("a=",a)
  5. ...: b = yield "bbbb"
  6. ...: print("b=",b)
  7. ...: c = yield "cccc"
  8. ...: print("c=",c)
  9. ...: yield "GAME OVER"
  10. ...:
  11. In[15]: gen = eat() # 获取⽣成器
  12. In[16]: ret1 = gen.__next__()
  13. ...: print(ret1)
  14. aaaa
  15. 1111
  16. In[17]: ret2 = gen.send("我send了一个参数给a")
  17. ...: print(ret2)
  18. a= send了一个参数给a # 可以看出send的数据是被上一个yield前的a给接收了
  19. bbbb
  20. In[18]: ret3 = gen.send("我send了一个参数给b")
  21. ...: print(ret3) # 这里send的数据也是被b接收了
  22. b= send了一个参数给b
  23. cccc
  24. In[19]: ret4 = gen.send("我send了一个参数给c")
  25. ...: print(ret4)
  26. c= send了一个参数给c
  27. GAME OVER

send和__next__():

  1. send和next()都是让⽣成器向下走⼀次
  2. send可以给上⼀个yield的位置传递值, 不能给最后⼀个yield发送值. 在第⼀次执⾏⽣成器代码的时候不能使⽤send()

2. 推导式

列表推导式

关于列表推导式,其实之前的文章中已经使用过,这里再正式介绍下;假设我们要打印1到20之间的奇数,照之前正常的写法我们要这么写:

  1. # 假设有一个需求,要写一个循环遍历1到20之间所有的奇数
  2. lst = []
  3. for i in range(1, 21):
  4. if i % 2 == 1:
  5. lst.append(i)
  6. print(lst)
  7. # 结果:
  8. # [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]

列表推导式的语法为:

  • 第一种只使用for循环遍历
  1. [expr for item in itratorable]
  2. # 相当于以下代码
  3. ret = []
  4. for item in iterable:
  5. ret.append(expr)
  • 第二种for循环遍历再加if条件判断
  1. [expr for item in iterable if cond]
  2. # 相当于以下结构代码
  3. ret = []
  4. for item in iterable:
  5. if cond:
  6. ret.append(expr)

第三种for循环加if双分支结构,注意此时的if/else语句要写在for语句前面

  1. [expr1 if cond else expr2 for item in iterable ]
  2. # 相当于以下代码
  3. ret = []
  4. for item in iterable:
  5. if cond:
  6. ret.append(expr1)
  7. else:
  8. ret.append(expr2)

对于上面的例子使用列表推导式可以这样写:

  1. # 使用推导式:
  2. lst = [i for i in range(1, 21) if i % 2 == 1]
  3. print(lst)
  4. # 结果:
  5. # [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]

使用列表推导式我们可以发现代码时精简了许多,而且代码的可读性更高了,其实还有一个优势是推导式速度更快:

  1. In [1]: %%timeit
  2. ...: lst1 = []
  3. ...: for i in range(10000):
  4. ...: lst1.append(i)
  5. ...:
  6. 788 µs ± 14.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
  7. In [2]: %%timeit
  8. ...: lst1 = [i for i in range(10000)]
  9. ...:
  10. 307 µs ± 1.84 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
  11. In [3]:

从上面的结果分析,使用列表推导式生成列表的方式要比普通for循环的效率要高很多

字典推导式

字典跟列表推导式的语法非常相似,使用{}括起来,然后在里面想列表推导式一样写自己的表达式即可:

  1. dic = {expr for k, v in iterable if cond} # 这里的expr表达式可以写成:k: v的形式
  2. # 相当于以下代码
  3. dic = dict()
  4. for k, v in iterable:
  5. if cond:
  6. expr(dic)

例如,把字典中的键值对都调换以下可以用如下方法:

  1. dic = {"张无忌":"赵敏", "杨过":"小龙女", "郭靖":"黄蓉"}
  2. # dic = {'k1':'v1', 'k2': 'v2', 'k3': 'v3'}
  3. dic = {v: k for k, v in dic.items()}
  4. print(dic)

生成器表达式

对于生成器表达式来说,只需要把列表推导式的中括号换成小括号就可以了:

  1. In[20]: def inc(x):
  2. ...: print('inc {0}'.format(x))
  3. ...: return x+1
  4. ...:
  5. In[21]: g = (inc(x) for x in range(10)) # 这里的g就是一个生成器对象
  6. In[22]: print(g)
  7. <generator object <genexpr> at 0x0000016F90161DB0>
  8. In[23]: print(g.__next__())
  9. inc 0
  10. 1
  11. In[24]: print(g.__next__()) # 也可以使用__next__方法取出一个值
  12. inc 1
  13. 2
  14. In[25]: print(g.__next__())
  15. inc 2
  16. 3
  17. In[26]: next(g) # 使用netx()和__next__()方法是一样的
  18. inc 3
  19. Out[26]: 4
  20. In[27]: next(g)
  21. inc 4
  22. Out[27]: 5

当然,生成器表达式也可以跟其他推导式一样套用if语句,其语法都是一样的,这里就不做介绍了。

⽣成器表达式和列表推导式的区别:

  • 列表推导式比较耗内存. ⼀次性加载. ⽣成器表达式⼏乎不占⽤内存. 使⽤的时候才分

    配和使⽤内存

  • 得到的值不⼀样. 列表推导式得到的是⼀个列表. ⽣成器表达式获取的是⼀个⽣成器.

⽣成器的惰性机制: ⽣成器只有在访问的时候才取值. 说⽩了. 你找他要他才给你值. 不找他

要. 他是不会执⾏的.

  1. def func():
  2. print(111)
  3. yield 222
  4. g = func() # ⽣成器g
  5. g1 = (i for i in g) # ⽣成器g1. 但是g1的数据来源于g
  6. g2 = (i for i in g1) # ⽣成器g2. 来源g1
  7. print(list(g)) # 获取g中的数据. 这时func()才会被执⾏. 打印111.获取到222. g完毕.
  8. print(list(g1)) # 获取g1中的数据. g1的数据来源是g. 但是g已经取完了. g1 也就没有数据了
  9. print(list(g2)) # 和g1同理
  10. # 注:list中有for的调用,可以迭代遍历生成器元素
  11. #结果:
  12. # 1111
  13. # [222]
  14. # []
  15. # []

访问生成器的另一种方法

使用yield from iterator语句

  1. In[28]: def test():
  2. ...: l1 = [1, 2, 3, 4]
  3. ...: l2 = ['a', 'b', 'c', 'd']
  4. ...: yield from l1 #
  5. ...: yield from l2
  6. ...:
  7. In[29]: g = test()
  8. In[30]: for i in g:
  9. ...: print(i)
  10. ...:
  11. 1
  12. 2
  13. 3
  14. 4
  15. a
  16. b
  17. c
  18. d

python学习笔记:第12天 列表推导式和生成器的更多相关文章

  1. Python 速通爆肝、列表推导式、生成器、装饰器、生命游戏

    列表推导式.赋值.切片(替换.插入).字符串处理与判断.enumerate().格式化字符串.读写文件.global 关键字.字符串startswith().类与对象.生成器.装饰器.Self.*ar ...

  2. Flutter学习笔记(12)--列表组件

    如需转载,请注明出处:Flutter学习笔记(12)--列表组件 在日常的产品项目需求中,经常会有列表展示类的需求,在Android中常用的做法是收集数据源,然后创建列表适配器Adapter,将数据源 ...

  3. Python学习笔记(二)——列表

    Python学习笔记(二)--列表 Python中的列表可以存放任何数据类型 >>> list1 = ['Hello','this','is','GUN',123,['I','Lov ...

  4. python之三元表达式、列表推导式、生成器表达式、递归、匿名函数、内置函数

    一 三元表达式.列表推导式.生成器表达式 一 三元表达式 name=input('姓名>>: ') res='SB' if name == 'alex' else 'NB' print(r ...

  5. python基础知识15---三元表达式、列表推导式、生成器表达式、递归、匿名函数、内置函数

    阅读目录 一 三元表达式.列表推导式.生成器表达式 二 递归与二分法 三 匿名函数 四 内置函数 五 阶段性练习 一. 三元表达式.列表推导式.生成器表达式 1 三元表达式 name=input('姓 ...

  6. Python进阶(四)----生成器、列表推导式、生成器推导式、匿名函数和内置函数

    Python进阶(四)----生成器.列表推导式.生成器推导式.匿名函数和内置函数 一丶生成器 本质: ​ 就是迭代器 生成器产生的方式: ​ 1.生成器函数

  7. python 三元表达式、列表推导式、生成器表达式、递归、匿名函数、内置函数

    http://www.cnblogs.com/linhaifeng/articles/7580830.html 三元表达式.列表推导式.生成器表达式.递归.匿名函数.内置函数

  8. python 三元表达式、列表推导式、生成器表达式

    一 三元表达式.列表推导式.生成器表达式 一 三元表达式 name=input('姓名>>: ') res='mm' if name == 'hahah' else 'NB' print( ...

  9. Python_迭代器、生成器、列表推导式,生成器表达式

    1.迭代器 (1)可迭代对象 s1 = ' for i in s1: print(i) 可迭代对象 示例结果: D:\Python36\python.exe "E:/Python/课堂视频/ ...

随机推荐

  1. Visio2010新建E-R图

    visio2010没有内置E-R图的模板,需要自己配置模具.步骤如下: 1.文件->新建->基本流程图->右键菱形->添加到新模具->命名为E-R图. 2.更多形状-&g ...

  2. 记一次JVM调优之旅(斗争full gc)

    俗话说技多不压身,当年苦读<深入理解JVM>还专门整理了笔记,现在就用上了- 笔记 http://www.cnblogs.com/syjkfind/p/3901774.html [症状]  ...

  3. 合并两个数组 以KEY 作为键

    <?php     $a= array(         array(             'ID'=> 2         ) );   $b= array(         arr ...

  4. 工作好搭档(四):铷安居 H-C81 电脑桌

    引言:工欲善其事,必先利其器.码农十年,与电脑打了二十多年的交道,也配置了一些过得去的装备.资金有限,更希望所有的投入都在刀刃上.写工作好搭档系列,是晒考虑的原因.思路.经验和教训.欢迎并希望大伙能一 ...

  5. ZT sem_init sem_wait sem_post sem_destroy

    sem_init() 2009-06-26 16:43:11|  分类: linux |字号 订阅       信号量的数据类型为结构sem_t,它本质上是一个长整型的数.函数sem_init()用来 ...

  6. Expression拼接

    public static class PBuilder { /// <summary> /// 机关函数应用True时:单个AND有效,多个AND有效:单个OR无效,多个OR无效:混应时 ...

  7. 阅读优秀的JAVA模板引擎Beetl的使用说明有感

    由于项目需要,对包括Beetl在内的JAVA模板引擎技术进行了学习 Beetl是由国人李家智(昵称闲大赋)开发的一款高性能JAVA模板引擎,对标产品是Freemaker 感慨于近几年国内开源项目的蓬勃 ...

  8. 关于iOS 3D touch 指纹验证的随笔

    file:///Users/OWen/Desktop/3DTouch.png 随着iOS系统不断的更新迭代,苹果总会推出一些新的功能,今天就研究了一下iOS8之后推出的指纹验证的功能,然后写了一个小d ...

  9. 在eclipse中查看HttpServlet源码失败的解决方法

    在初次建立java EE 项目时,想要查看HttpServlet源码时会提示失败, 按照网上的方式,将Tomcat中lib中的servlet-api.jar的包导进去,发现并不管用.并且提示里面并不包 ...

  10. NodeJs——入门

    关于NPM: npm 是 nodejs 的包管理和分发工具.它可以让 javascript 开发者能够更加轻松的共享代码和共用代码片段,并且通过 npm 管理你分享的代码也很方便快捷和简单. 一 No ...