1. 迭代器

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

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

生成器函数

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

In[2]: def func1():
...: print('aaaa')
...: return 1111
...:
In[3]: fun = func1()
aaaa
In[4]: print(fun)
1111

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

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

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

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

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

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

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

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

send方法

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

In[14]: def eat():
...: print("aaaa")
...: a = yield 1111
...: print("a=",a)
...: b = yield "bbbb"
...: print("b=",b)
...: c = yield "cccc"
...: print("c=",c)
...: yield "GAME OVER"
...:
In[15]: gen = eat() # 获取⽣成器
In[16]: ret1 = gen.__next__()
...: print(ret1)
aaaa
1111
In[17]: ret2 = gen.send("我send了一个参数给a")
...: print(ret2)
a= 我send了一个参数给a # 可以看出send的数据是被上一个yield前的a给接收了
bbbb
In[18]: ret3 = gen.send("我send了一个参数给b")
...: print(ret3) # 这里send的数据也是被b接收了
b= 我send了一个参数给b
cccc
In[19]: ret4 = gen.send("我send了一个参数给c")
...: print(ret4)
c= 我send了一个参数给c
GAME OVER

send和__next__():

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

2. 推导式

列表推导式

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

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

列表推导式的语法为:

  • 第一种只使用for循环遍历
[expr for item in itratorable]

# 相当于以下代码
ret = []
for item in iterable:
ret.append(expr)
  • 第二种for循环遍历再加if条件判断
[expr for item in iterable if cond]

# 相当于以下结构代码
ret = []
for item in iterable:
if cond:
ret.append(expr)

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

[expr1 if cond else expr2 for item in iterable ]

# 相当于以下代码
ret = []
for item in iterable:
if cond:
ret.append(expr1)
else:
ret.append(expr2)

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

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

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

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

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

字典推导式

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

dic = {expr for k, v in iterable if cond}        # 这里的expr表达式可以写成:k: v的形式

# 相当于以下代码
dic = dict()
for k, v in iterable:
if cond:
expr(dic)

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

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

生成器表达式

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

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

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

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

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

    配和使⽤内存

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

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

要. 他是不会执⾏的.

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

访问生成器的另一种方法

使用yield from iterator语句

In[28]: def test():
...: l1 = [1, 2, 3, 4]
...: l2 = ['a', 'b', 'c', 'd']
...: yield from l1 #
...: yield from l2
...:
In[29]: g = test()
In[30]: for i in g:
...: print(i)
...:
1
2
3
4
a
b
c
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. How I explained Design Patterns to my wife: Part 1

    Introduction Me and my wife had some interesting conversations on Object Oriented Design principles. ...

  2. 【Leetcode】【Medium】Unique Binary Search Trees

    Given n, how many structurally unique BST's (binary search trees) that store values 1...n? For examp ...

  3. javascript 随机数 生成 n-m

    例子:生成800-1500的随机整数,包含800但不包含1500 代码如下: 1500-800 = 700 Math.random()*700 var num = Math.random()*700 ...

  4. C++的虚析构

    最近准备复习一遍所有的知识点,先从基础开始做起,用几分钟写个继承和析构吧. 父类为A,子类为B,代码如下: class A { public: A() { cout << "构造 ...

  5. [工具教程] HBuilder调试夜神安卓模拟器方法(该方法真实有效)

    HBuilder调试夜神安卓模拟器方法 现在开发手机app的IDE很多,今天我就以我个人开发使用的HBuider开发工具讲一下手机app开发调试.HBuider支持真机调试,这个比较简单,只要安装好手 ...

  6. JavaScript的DOM_其他的扩展

    一.呈现模式 从 IE6 开始开始区分标准模式和混杂模式(怪异模式),主要是看文档的声明. IE 为document 对象添加了一个名为 compatMode 属性,这个属性可以识别 IE 浏览器的文 ...

  7. AtCoder Grand Contest

    一句话题解 QwQ主要是因为这篇文章写的有点长……有时候要找某一个题可能不是很好找,所以写了这个东西. 具体的题意.题解和代码可以再往下翻._(:з」∠)_ AGC 001 C:枚举中点/中边. D: ...

  8. CF#538(div2) B. Yet Another Array Partitioning Task 【YY】

    任意门:http://codeforces.com/contest/1114/problem/B B. Yet Another Array Partitioning Task time limit p ...

  9. Linux学习总结(十三)文本编辑器 vim

    vim是vi的升级版,会根据文本属性带色彩显示,具体用法如下: 一般模式 : 1.光标定位: 左右移动一个字符, h l上下移动一个字符, k j左右下上 ,左右在两边,下上在中间这样记光标定位行首 ...

  10. Linux网络配置&进程管理

     原理图 查看ip和网关