Python 迭代器之列表解析与生成器

1. 列表解析
1.1 列表解析基础
列表解析把任意一个表达式应用到一个迭代对象中的元素
Python内置ord
函数会返回一个字符的ASCII整数编码(chr
函数是它的逆过程, 它将ASCII整数编码转化为字符)
>>> ord('1')
49
>>> ord('A')
65
>>> chr(65)
'A'
>>>
如果我们需要收集一个字符串的所有字符的ASCII编码,最直接的方法是使用一个简单的for循环
>>> res = []
>>> for e in "apple":
res.append(ord(e))
>>> res
[97, 112, 112, 108, 101]
>>>
使用map()内置函数, 使用一个简单的函数调用, 而不必关心代码中列表的结构;
>> list(map(ord,"apple"))
[97, 112, 112, 108, 101]
>>>
map将一个函数映射遍一个序列, 列表解析把一个表达式映射遍一个序列,将其结果收集到一个新的列表
并返回. 列表解析由方括号[ ]
封装起来, 表示返回的是一个列表
>>> [ord(e) for e in 'apple']
[97, 112, 112, 108, 101]
>>>
>>> [ e**2 for e in range(4)]
[0, 1, 4, 9]
>>> list(map(lambda x:x**2, range(4)))
[0, 1, 4, 9]
>>>
1.2 增加测试和循环嵌套
列表解析可以在for之后编写一个if子句, 来增加选择逻辑.
>>> [ e for e in range(10) if e%2 == 0]
[0, 2, 4, 6, 8]
>>> list(filter(lambda x:x%2 == 0, range(10)))
[0, 2, 4, 6, 8]
>>> res = []
>>> for e in range(10):
if e%2 == 0:
res.append(e)
>>> res
[0, 2, 4, 6, 8]
实际上, 列表解析还能够更加通用. 你可以在一个列表中编写任意数量的嵌套for循环, 并且每次都有可选的关联的if测试,通用的列表解析的结构如下所示:
[expression for target1 in iterable1 [if condition1]
for target2 in iterable2 [if condition2]
...
for targetN in iterableN [if conditionN] ]
当for分句嵌套在列表解析中时, 他们工作起来就像等效的嵌套的for循环语句. 例如如下代码
>>> [x + y for x in [1, 2, 3] for y in [100, 200, 300]]
[101, 201, 301, 102, 202, 302, 103, 203, 303]
>>>
等效于
>>> res = []
>>> for x in [1, 2, 3]:
for y in [100, 200, 300]:
res.append(x+y)
>>> res
[101, 201, 301, 102, 202, 302, 103, 203, 303]
for嵌套 + if子句
>>> [(x, y) for x in range(5) if x%2==1 for y in range(5) if y%2==0]
[(1, 0), (1, 2), (1, 4), (3, 0), (3, 2), (3, 4)]
>>>
等价于
>>> res = []
>>> for x in range(5):
if x%2 == 1:
for y in range(5):
if y%2 == 0:
res.append((x, y))
>>> res
[(1, 0), (1, 2), (1, 4), (3, 0), (3, 2), (3, 4)]
列表解析与矩阵
>>> M = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
>>> M
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> [ row[0] for row in M]
[1, 4, 7]
>>>
1.3 列表解析性能
一般来说, map调用比等效的for循环快两倍, 而列表解析往往比map调用要稍快一些. 速度距离来自底层, map和列表解析在解释器中以C语言的速度运行, 比Python的for循环代码在PVM中要运行的快的多.
2. 生成器
如今Python对延迟提供了更多的支持---它提供了工具在需要的时候才能产生结果, 而不是立即产生结果.特别的有着两种语言结构尽可能的延迟结果创建.
生成器函数: 编写为常规的def语句, 但是使用yield一次返回一个结果, 在每个结果之间挂起和继续它们的状况
生成器表达式: 类似于列表解析,但是它们返回按需产生结果的一个对象, 而不是构建一个结果列表
由于二者都不会一次性构建一个列表, 它们节省了内存空间, 并且允许计算时间分散到各个结果的请求,我们将会看到二者都是通过实现迭代协议来执行它们的延迟结果的魔法.
2.1 生成器函数: yield vs return
普通函数的处理流程是接受输入参数,处理并立即送回单个结果; 生成器函数
的处理流程是接受输入参数,处理,送回一个值并随后从其退出的地方继续的函数,生成器函数会随着时间生成一个序列.
生成器函数使用常规的def语句编写, 然而创建时,会自动实现迭代协议, 一般可以出现在迭代环境中.
2.1.1 状态挂起
和返回一个值并退出的常规函数不同, 生成器函数自动在生成值的时刻挂起并继续函数的执行.
由于生成器函数在挂起的时刻保存的状态包含它们的整个本地作用域, 当函数恢复时, 他们的本地变量保持了信息并且使其可用.
和常规函数的区别主要的代码的区别:
1) 生成器 `yields` 一个值, 而不是返回一个值;
2) yield语句挂起该函数并想调用者发送一个值, 但是保留足够的状态以使函数能从它们离开的地方继续;
3) 当继续时, 函数在上一个yield返回后立即继续执行.从函数角度看, 这允许代码随时间产生一系列值,俄入世一次计算他们并在诸如列表的内容中送回他们.
2.1.2 迭代协议整合
生成器函数和Python的迭代协议的密切相关;
可迭代对象定义了一个__next__
方法,它要么返回下一项,要么返回特殊的StopIteration来终止异常;
如果支持这种协议, Python迭代器使用这种迭代协议来遍历一个序列或值生成器.
>>> help(iter)
Help on built-in function iter in module builtins:
iter(...)
iter(iterable) -> iterator
iter(callable, sentinel) -> iterator
Get an iterator from an object.
In the first form, the argument must supply its own iterator, or be a sequence.
In the second form, the callable is called until it returns the sentinel.
要支持这一协议,函数包含一条yield语句,该语句特别编译为生成器。当调用时,它们返回一个迭代器对象,该对象支持用一个名为__next__
的自动创建的方法来继续执行的接口。
生成器函数也可能有一条return语句,总是在def语句块的末尾,直接终止值的生成。
从调用者的角度来看,生成器的__next__方法继续函数并且运行到下一个yield结果返回或引发一个StopIteration异常。
生成器函数,编写为包含yield语句的def语句,自动地支持迭代协议,并且由此可能用在任何迭代环境中随着时间并根据需要产生结果。
2.1.3 生成器函数应用
为了讲清楚基础知识,请看如下代码,它定义了一个生成器函数,这个函数将会用来不断地生成一些列的数字的平方。
def gensquares(N):
for i in range(N):
yield i ** 2
这个函数在每次循环时都会产生一个值,之后将其返还给它的调用者。当它被暂停后,它的上一个状态保存了下来,并且在yield语句之后控制器马上被回收。
例如,当用在一个for循环中时,在循环中每一次完成函数的yield语句后,控制权都会返还给函数。
for i in gensquares(5):
print(i, end=" : ")
0 : 1 : 4 : 9 : 16 :
为了终止生成值,函数可以使用给一个无值的返回语句,或者在函数体最后简单的让控制器脱离。
如果想要看看在for里面发生了什么,直接调用一个生成器函数:
>>> x = gensquares(4)
>>> x
<generator object gensquares at 0x0000014EF59FEDB0>
得到的是一个生成器对象,它支持迭代器协议,也就是生成器对象有一个__next__方法,它可以开始这个函数,或者从它上次yield值后的地方恢复,并且在得到一系列的值的最后一个时,产生StopIteration异常。
2.1.4 扩展生成器函数协议:send 和 next
send方法生成一系列结果的下一个元素,这一点就像__next__方法一样,但是它也提供了一种调用者与生成器之间进行通信的方法,从而能够影响它的操作。
从技术上来说,yield现在是一个表达式的形式,可以返回传入的元素来发送,而不是一个语句[尽管无论哪种叫法都可以:作为yield X 或者 A = (yield X)]。表达式必须包括在括号中,除非它是赋值语句右边的唯一一项。 例如,X = yield Y没问题,就如同 X = (yield Y) + 42。
当使用这一额外的协议时,值可以通过调用G.send(value)发送给一个生成器G。
>>> def gen():
for i in range(10):
X = yield i
print(X)
>>> G = gen()
>>> next(G) # next() 开始生成器
0
>>> G.send(77) # 高级的的send方法 发送参数给生成器表达式
77
1
>>> G.send(88)
88
2
>>> next(G) # 返回None
None
3
2.2 生成器表达式:迭代器遇到列表解析
在最新版本的Python中,迭代器和列表解析的概念形成了这种语言的一个新的特性,生成器表达式。 从语法上来讲,生成器表达式就像一般的列表解析一样,但是它们是括在圆括号中而不是方括号中的。
>>> [x ** 2 for x in range(4)]
[0, 1, 4, 9]
>>> (x ** 2 for x in range(4)) # 生成器表达式
<generator object <genexpr> at 0x0000014EF59FEDB0>
实际上,至少在一个函数的基础上,编写一个列表解析基本上等同于:在一个list内置调用中包含一个生成器表达式以迫使其一次生成列表中所有的结果。
>>> list(x ** 2 for x in range(4))
[0, 1, 4, 9]
尽管如此,从执行过程上来讲,生成器表达式很不相同:不是在内存中构建结果,而是返回一个生成器对象,这个对象将会支持迭代协议并在任意的迭代语境的操作中。
>>> G = (x ** 2 for x in range(4))
>>> next(G)
0
>>> next(G)
1
>>> next(G)
4
>>> next(G)
9
>>> next(G)
Traceback (most recent call last):
File "<pyshell#99>", line 1, in <module>
next(G)
StopIteration
我们一般不会机械地使用next迭代器来操作生成器表达式,因为for循环会自动触发。
>>> for num in (x ** 2 for x in range(4)):
print("%s, %s" % (num, num / 2.0))
0, 0.0
1, 0.5
4, 2.0
9, 4.5
实际上,每一个迭代的语境都会这样,包括sum、map 和 sorted等内置函数,以及在前面涉及的其他迭代语境,例如 any、all 和 list内置函数等。
注意,如果生成器表达式是在其他的括号之内,就像在那些函数调用之中,这种情况下,生成器自身的括号就不是必须的了。 尽管这样,在下面第二个sorted调用中,还是需要额外的括号。
>>> sum(x ** 2 for x in range(4))
14
>>> sorted(x ** 2 for x in range(4))
[0, 1, 4, 9]
>>> sorted((x ** 2 for x in range(4)), reverse=True)
[9, 4, 1, 0]
>>> import math
>>> list( map(math.sqrt, (x ** 2 for x in range(4))) )
[0.0, 1.0, 2.0, 3.0]
生成器表达式大体上可以认为是内存空间的优化,它们不需要像方括号的列表解析一样,一次构造出整个列表。它们在实际中运行起来可能稍慢一些,所以它们可能对于非常大的结果集合的运算来说是最优的选择。关于性能的更权威的评价,必须等到最后编写计时脚本的时候给出。
2.3 生成器函数 VS 生成器表达式
有趣的是,同样的迭代往往可以用一个生成器函数或一个生成器表达式编写。例如,如下的生成式表达式,把一个字符串中的每个字母重复4次。
>>> G = (c * 4 for c in "SPAM")
>>> list(G)
['SSSS', 'PPPP', 'AAAA', 'MMMM']
等价的生成器函数需要略微多一些的代码,但是,作为一个多语句的函数,如果需要的话,它将能够编写更多的逻辑并使用更多的状态信息。
>>> def timesfour(S):
for c in S:
yield c * 4
>>> G = timesfour("spam")
>>> list(G)
['ssss', 'pppp', 'aaaa', 'mmmm']
# 表达式和函数支持自动迭代和手动迭代……前面的列表自动调用迭代,如下的迭代手动进行。
>>> G = (c * 4 for c in "SPAM")
>>> i = iter(G)
>>> next(i)
'SSSS'
>>> next(i)
'PPPP'
2.4 生成器是单迭代器对象
生成器函数和生成器表达式自身都是迭代器,并由此只支持一次活跃迭代.
>>> G = (c * 4 for c in "SPAM")
>>> iter(G) is G
True
# 如果你手动地使用多个迭代器来迭代结果流,它们将会指向相同的位置。
>>> G = (c * 4 for c in "SPAM") # 新生成器表达式
>>> I1 = iter(G)
>>> next(I1)
'SSSS'
>>> next(I1)
'PPPP'
>>> I2 = iter(G) # ----
>>> next(I2)
'AAAA'
此外,一旦任何迭代器运行到完成,所有的迭代器都将用尽,我们必须产生一个新的生成器以再次开始。
>>> list(I1) # 自动迭代
['MMMM']
>>> next(I2) # I2的手动迭代
StopIteration # 异常
>>> I3 = iter(G) # 生成新的迭代器(其实不会生成新的)
>>> next(I3)
Traceback (most recent call last):
File "<pyshell#158>", line 1, in <module>
next(I3)
StopIteration # 仍旧迭代异常
>>> I3 = iter(c * 4 for c in "SPAM") # 新的迭代器
>>> next(I3) # 开始迭代
'SSSS'
对于生成器函数来说,也是如此,如下的基于语句的def等价形式只支持一个活跃的生成器并且在一次迭代之后用尽。
>>> def timesfour(S):
for c in S:
yield c * 4
>>> G = timesfour("spam")
>>> iter(G) is G
True
>>> I1, I2 = iter(G), iter(G)
>>> next(I1)
'ssss'
>>> next(I1)
'pppp'
>>> next(I2)
'aaaa'
这与某些内置类型的行为不同,它们支持多个迭代器并且在一个活动迭代器中传递并反映它们的原处修改。
>>> L = [1, 2, 3, 4]
>>> I1, I2 = iter(L), iter(L)
>>> next(I1)
1
>>> next(I1)
2
>>> next(I2)
1
>>> del L[2:]
>>> next(I1)
StopIteration
Python 迭代器之列表解析与生成器的更多相关文章
- Python 迭代器之列表解析
 [TOC] 尽管while和for循环能够执行大多数重复性任务, 但是由于序列的迭代需求如此常见和广泛, 以至于Python提供了额外的工具以使其更简单和高效. 迭代器在Python中是以C语言的 ...
- Python中的列表解析和生成器表达式
Python中的列表解析和生成器表达式 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.列表解析案例 #!/usr/bin/env python #_*_coding:utf-8 ...
- Python学习笔记(迭代,列表解析,生成器)
迭代(iterable):支持每次返回自己所包含一个对象的 iter()得到迭代器,.next()遍历列表. 列表解析:根据已有列表高效生成列表的方式. 生成器(Generator): 通过列表生成式 ...
- 3、Python迭代器、列表解析及生成器(0530)
1.动态语言 sys.getrefcount() //查看对象的引用计数 增加对象的引用计数场景 对象创建时:以赋值的方式,创建变量名的同时就会创建变量 将对象添加进容器时:类似list.app ...
- Python全栈day18(三元运算,列表解析,生成器表达式)
一,什么是生成器 可以理解为一种数据类型,这种数据类型自动实现了迭代器协议(其他数据类型需要调用自己内置的__iter__方法),所以生成器是可迭代对象. 二,生成器分类在python中的表现形式 1 ...
- Python基础(9)三元表达式、列表解析、生成器表达式
一.三元表达式 三元运算,是对简单的条件语句的缩写. # if条件语句 if x > f: print(x) else: print(y) # 条件成立左边,不成立右边 x if x > ...
- python的迭代器、生成器、三元运算、列表解析、生成器表达式
一 迭代的概念 迭代是Python最强大的功能之一,是访问集合元素的一种方式. 迭代器是一个可以记住遍历的位置的对象. 迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束.迭代器只能往前 ...
- Python基础:08列表解析与生成器表达式
一:列表解析 列表解析(List comprehensions)来自函数式编程语言Haskell .它可以用来动态地创建列表.它在 Python 2.0 中被加入. 列表解析的语法: [exp ...
- python的高级特性:切片,迭代,列表生成式,生成器,迭代器
python的高级特性:切片,迭代,列表生成式,生成器,迭代器 #演示切片 k="abcdefghijklmnopqrstuvwxyz" #取前5个元素 k[0:5] k[:5] ...
随机推荐
- 微信小程序天坑--图片汉字命名
图片用汉字命名的,在开发者工具中是显示的,但是,在真机的微信中,是不会显示的. 大写的尴尬,微信小程序开发者工具对于做微信的UI来说,就是一个天坑,在电脑上漂漂亮亮的,到手机上各种意想不到的情况.
- VM快照-克隆重要应用讲解及克隆后网卡问题解决
快照:snapshot 1---2---3---5 用于以后 rollback 1 2 3 5 克隆前关机:halt 克隆之后连不上网 解决办法: 1.编辑eth0的配置文 vi/etc/syscon ...
- Java求最大公约数和最小公倍数
最大公约数(Greatest Common Divisor(GCD)) 基本概念 最大公因数,也称最大公约数.最大公因子,指两个或多个整数共有约数中最大的一个.a,b的最大公约数记为(a,b),同样的 ...
- Web前端有价值的博客文章汇总
一.HTML 二.CSS 1.深入理解position和z-index属性 :https://www.cnblogs.com/zhuzhenwei918/p/6112034.html 2.BFC(清除 ...
- Bootstrap 在手机页时,导航下拉自动回收
$(".menu-main").collapse("hide"); //.menu-main就是下来导航的类名
- Problem : 1002 ( A + B Problem II )
经验总结:一定要注意输出的格式,字符的空格,空行,一定要观察清楚.如本题的最后一个输出结果后面没有空行.最后代码实现的时候需要判断一下,代码如下 !=n) cout<<endl; Prob ...
- asp.net中http接口的开发
第一篇博客,如有不足请大家多多谅解. 最近一段时间主导着一个app的开发.所有功能都交给后台接口进行处理.采用http,传输的数据类型为json. http接口是一种基于基于TCP.http服务的ap ...
- spring boot rest例子
简介: 本文将帮助您使用 Spring Boot 创建简单的 REST 服务. 你将学习 什么是 REST 服务? 如何使用 Spring Initializr 引导创建 Rest 服务应用程序? 如 ...
- java File类常用方法
file类常用方法 delete()删除此抽象路径名表示的文件和目录. equals()测试此抽象路径名与给定对象是否相等. exists()测试此抽象路径名表示的文件或目录是否存在. getName ...
- 【Python】 配置解析ConfigParser & 命令行参数解析optparser
ConfigParser ConfigParser包装了配置文件的读取和写入,使得python程序可以更加轻松操作配置文件了.这里的配置文件是指.ini的那种文件,基本格式如下 [section_a] ...