用迭代工具模拟zip和map

======================================================================

我们已经知道了zip怎样组合可迭代对象,也知道了map怎样映射函数。

>>> S1 = 'abc'
>>> S2 = 'xyz123'
>>> list(zip(S1,S2))
[('a', 'x'), ('b', 'y'), ('c', 'z')] >>> list(zip([-2,-1,0,1,2]))
[(-2,), (-1,), (0,), (1,), (2,)] >>> list(map(abs,(-2,-1,0,1,2)))
[2, 1, 0, 1, 2] >>> list(map(pow,[1,2,3],[2,3,4,5]))
[1, 8, 81]

---------------------------------------------------------------------------------------------------------------

以下就能够编写自己的map(func,...)了

>>> def mymap(func,*seqs):
res = []
for args in zip(*seqs):
res.append(func(*args))
return res >>> print(mymap(abs,(-2,-1,0,1,2)))
[2, 1, 0, 1, 2]
>>> print(mymap(pow,[1,2,3],[2,3,4,5]))
[1, 8, 81]

这个版本号依赖于特殊的*args參数传递语法。它手机多个序列參数,将其作为zip參数解包以便组合,然后成对的zip结果解包作为參数以便传入到函数。

也就是说,我们在使用这种一个事实,zip是map中的一个主要的嵌套操作。

【一定要掌握写这样的函数的高级技巧,对參数的处理!】

然而。实际上,前面的版本号展示了列表解析模式,在一个for循环中构建操作结果的一个列表。所以,这个函数还能够精简:

>>> def mymap(func,*seqs):
return [func(*args) for args in zip(*seqs)] >>> print(mymap(abs,(-2,-1,0,1,2)))
[2, 1, 0, 1, 2]
>>> print(mymap(pow,[1,2,3],[2,3,4,5]))
[1, 8, 81]

当这段代码执行的时候,结果与前面同样,可是。这段代码更加精炼而且可能执行地更快。

只是。这两个版本号都是一次性构建结果列表,对于较大的列表来说,这可能浪费内存。既然已经知道了生成器函数和表达式,又一次编码这两种替代方案来依据需求产生结果是非常easy的:

>>> def mymap(func,*seqs):
for args in zip(*seqs):
yield func(*args) >>> def mymap(func,*seqs):
return (func(*args) for args in zip(*seqs)) >>> print(list(mymap(pow,[1,2,3],[2,3,4,5])))
[1, 8, 81]

当这段代码执行的时候,结果与前面同样。可是,这段代码更加精炼而且可能执行地更快。



只是。这两个版本号都是一次性构建结果列表,对于较大的列表来说,这可能浪费内存。既然已经知道了生成器函数和表达式。又一次编码这两种替代方案来依据需求产生结果是非常easy的:

>>> def mymap(func,*seqs):
for args in zip(*seqs):
yield func(*args) >>> def mymap(func,*seqs):
return (func(*args) for args in zip(*seqs)) >>> print(list(mymap(pow,[1,2,3],[2,3,4,5])))
[1, 8, 81]

生成器的版本号产生相同的结果,可是返回设计用来支持迭代协议的生成器。

第一个版本号每次yield一个结果。第二个版本号返回一个生成器表达式的结果做相同的事情。

---------------------------------------------------------------------------------------------------------------

编写自己的zip(...)

前述的样例中的魔力在于,它们使用zip内置函数来配对来自多个序列的參数。

以下。我们也来模拟内置的zip。

>>> def myzip(*seqs):
seqs = [list(S) for S in seqs]
res = []
while all(seqs):
res.append(tuple(S.pop(0) for S in seqs))
return res >>> S1,S2 = 'abc','xyz123'
>>> print(myzip(S1,S2))
[('a', 'x'), ('b', 'y'), ('c', 'z')]

注意这里的all的内置函数的使用,假设一个可迭代对象中的全部元素为True(或者对等的为非空),它返回True。当列表中有參数在删除后变为了空,这个内置函数用来停止循环。

然而,和前面一样。既然我们的zip构建并返回列表。用yield将它们转换为生成器以便它们每一个都是每次返回结果中的一项,这样来节省内存。

>>> def myzip(*seqs):
seqs = [list(S) for S in seqs]
while all(seqs):
yield tuple(S.pop(0) for S in seqs) >>> list(myzip([1,2,3],('a','b','c','d')))
[(1, 'a'), (2, 'b'), (3, 'c')]

当然,也能够通过计算最小的參数长度来完毕其工作,有了最小长度,非常easy编写编写嵌套的列表解析来遍历參数索引范围

>>> def myzip(*seqs):
minlen = min(len(S) for S in seqs)
return [tuple(S[i] for S in seqs) for i in range(minlen)] >>> myzip([1,2,3],('a','b','c','d'))
[(1, 'a'), (2, 'b'), (3, 'c')]
>>> def myzip(*seqs):
minlen = min(len(S) for S in seqs)
for i in range(minlen):
yield tuple(S[i] for S in seqs) >>> list(myzip([1,2,3],('a','b','c','d')))
[(1, 'a'), (2, 'b'), (3, 'c')]

这里第一个样例返回了一个列表。第二个样例使用了生成器函数。但事实上这里也能够使用生成器表达式,反而更精炼:

>>> def myzip(*seqs):
minlen = min(len(S) for S in seqs)
return (tuple(S[i] for S in seqs) for i in range(minlen)) >>> myzip([1,2,3],('a','b','c','d'))
<generator object <genexpr> at 0x02BF1B98>
>>> list(myzip([1,2,3],('a','b','c','d')))
[(1, 'a'), (2, 'b'), (3, 'c')]

======================================================================

对迭代的各种方法进行计时

列表解析要比for循环语句有速度方面的性能优势,并且map会根据调用方法的不同表现出更好或更差的性能。生成器表达式看起来比列表解析速度更慢一些。可是它们把内存需求降到了最小。

---------------------------------------------------------------------------------------------------------------

对模块计时

Python对代码计时非常easy。要看看迭代选项是怎样叠加起来的,让我们从编写一个模块文件里的简单但通用的计时器工具函数開始,从而使其能够用于各类程序中。

#File mytimer.py

import time
reps = 1000
repslist = range(reps) def timer(func,*pargs,**kargs):
start = time.clock()
for i in repslist:
ret = func(*pargs,**kargs)
elapsed = time.clock() - start
return (elapsed,ret)

这个模块通过获取时间開始、调用函数固定的次数而且用開始时间减去停止时间。从而对不论什么位置和keyword參数调用随意函数进行计时。

注意下面几点:

(1)Python的time模块同意訪问当前时间。精度随着每一个平台而有所不同。

在Windows上,这个调用号称可以达到微妙的精度。已经相当准确了。

(2)range调用放到了计时循环之外,由于。它的构建成本不会计算到Python2.6的计时器函数中。

在Python3.0中的range是一个迭代器。因此这个步骤是不须要的。

(3)reps计数是一个全局变量,假设须要的话,导入者能够改动它:mytimer.reps = N



当这些完毕后,全部调用的总的时间在一个元祖中返回,还带有被计时的函数的终于返回值,以便调用者能够验证其操作。

---------------------------------------------------------------------------------------------------------------

计时脚本

如今,要计时迭代工具的速度,执行例如以下脚本,它使用已经学习过的各种列表构建技术的相对速度的计时器模块。

# File timeseqs.py

import mytimer,sys
reps = 10000
repslist = range(reps) def forLoop():
res = []
for x in repslist:
res.append(abs(x))
return res def listComp():
return [abs(x) for x in repslist] def mapCall():
return list(map(abs,repslist)) def genExpr():
return list(abs(x) for x in repslist) def genFunc():
def gen():
for x in repslist:
yield abs(x)
return list(gen()) print(sys.version)
for test in (forLoop,listComp,mapCall,genExpr,genFunc):
elapsed,result = mytimer.timer(test)
print('-'*33)
print('%-9s:%.5f => [%s...%s]'%(test.__name__,elapsed,result[0],result[-1]))

这段脚本測试了五种构建结果列表的替代方法。而且,每种方法都运行了一千万次级别的步骤。也就是说。五个測试中的每个都构建了拥有10000个元素的列表1000次。

要注意,底部的代码怎样遍历4个函数对象的一个元祖并打印出每个__name__,这是一个内置的属性。

---------------------------------------------------------------------------------------------------------------

计时结果

在我的Window10电脑上測试。输出了例如以下结果——map比列表解析稍微快一点。但二者都比for循环要快非常多,而且生成器表达式和生成函数速度居中。

>>>
3.4.3 (v3.4.3:9b73f1c3e601, Feb 24 2015, 22:43:06) [MSC v.1600 32 bit (Intel)]
---------------------------------
forLoop :1.20765 => [0...9999]
---------------------------------
listComp :0.77999 => [0...9999]
---------------------------------
mapCall :0.67569 => [0...9999]
---------------------------------
genExpr :0.91294 => [0...9999]
---------------------------------
genFunc :0.87446 => [0...9999]

假设改动这段脚本。在每次迭代上运行一个真正的操作(如加法)。而不是调用abs这种小的内置函数,五个函数改为:

def forLoop():
res = []
for x in repslist:
res.append(x+10)
return res def listComp():
return [x+10 for x in repslist] def mapCall():
return list(map(lambda x:x+10,repslist)) def genExpr():
return list(x+10 for x in repslist) def genFunc():
def gen():
for x in repslist:
yield x+10
return list(gen())

看看測试结果:

>>>
3.4.3 (v3.4.3:9b73f1c3e601, Feb 24 2015, 22:43:06) [MSC v.1600 32 bit (Intel)]
---------------------------------
forLoop :1.12030 => [10...10009]
---------------------------------
listComp :0.64926 => [10...10009]
---------------------------------
mapCall :1.35093 => [10...10009]
---------------------------------
genExpr :0.79881 => [10...10009]
---------------------------------
genFunc :0.85247 => [10...10009]

这时候,针对map调用的是自己定义的一个lambda函数,所以它比for循环慢。但列表解析依旧是最快的。

解释器优化是内部化的一个问题。像这样对Python代码进行性能分析是一件很须要技术的事情。其实不可能推測哪种方法会运行地更好。并且性能应该不是我们编写Python代码时首要关心的问题——要优化Python代码,首先为了可读性和简单性而编写代码。然后,假设须要的话,再优化。

======================================================================

问题:



1.生成器和迭代器有什么关系?



生成器是支持迭代协议的对象:它们有__next__方法。反复前进到系列结果中的下个元素,以及到系列尾端时引发例外事件。在Python中,我们能够用def、加圆括号的列表解析的生成器表达式以及以类定义特殊方法__iter__来创建生成器对象,通过它们来编写生成器函数。



2.yield语句是做什么的?



当有了yield语句时,这个语句会让Python把函数特定的编译成生成器。当调用时。会返回生成器对象,支持迭代协议。

当yield语句执行时,会把结果返回给调用者,让函数的状态挂起。然后。当调用者再调用__next__方法时。这个函数就能够又一次在上次yield语句后继续执行。生成器也能够有return语句。用来终止生成器。

最新文章

  1. Android okHttp网络请求之文件上传下载
  2. Linux学习 :移植linux-4.7.4到JZ2440开发板
  3. Jmeter多机并发压测IP地址问题
  4. crontabs Permission denied
  5. 面向生产环境的大集群模式安装Hadoop
  6. uva 12648
  7. POJ 3687 Labeling Balls 逆向建图,拓扑排序
  8. 分析ECMall的注册与登录机制
  9. linux c数据库备份第二版
  10. android http协议post请求方式
  11. win10 uwp 打开文件管理器选择文件
  12. LinkedBlockingQueue源码解析
  13. 单元测试-unittest模块
  14. [译]《Sphinx权威指南》 - Sphinx入门
  15. vue中axios 配置请求拦截功能 及请求方式如何封装
  16. php 排列组合函数(无重复组合,可重复组合【全排列组合】)
  17. NodeJs进击,新建一个Node Server
  18. TS 基础数据类型
  19. 在ContextLoaderListener中使用注解注入的类和job中使用注解注入的类
  20. python---函数补充(变量传递),语句执行顺序(入栈顺序)

热门文章

  1. MSSQL服务器 电脑改名后配置
  2. java 实现将java对象转为yaml文件
  3. bug,实现类未找到service
  4. 使用 CSS 追踪用户
  5. JavaScript中变量的类型
  6. 设置Hadoop的 dataNode的单个Map的内存配置
  7. 安卓代码迁移:Program "sh" not found in PATH
  8. OpenCV: 图像连通域检测的递归算法
  9. 如何查看Linux的CPU负载
  10. MySQL基础命令小结