Python——迭代器和解析(3)

44 篇文章 0 订阅
44 篇文章 21 订阅
用迭代工具模拟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]
当这段代码运行的时候,结果与前面相同,但是,这段代码更加精炼并且可能运行地更快。
不过,这两个版本都是一次性构建结果列表,对于较大的列表来说,这可能浪费内存。既然已经知道了生成器函数和表达式,重新编码这两种替代方案来根据需求产生结果是很容易的:

>>> 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]
当这段代码运行的时候,结果与前面相同,但是,这段代码更加精炼并且可能运行地更快。

不过,这两个版本都是一次性构建结果列表,对于较大的列表来说,这可能浪费内存。既然已经知道了生成器函数和表达式,重新编码这两种替代方案来根据需求产生结果是很容易的:

>>> 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')]
当然,也可以通过计算最小的参数长度来完成其工作,有了最小长度,很容易编写编写嵌套的列表解析来遍历参数索引范围
>>> 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对代码计时很容易。要看看迭代选项是如何叠加起来的,让我们从编写一个模块文件中的简单但通用的计时器工具函数开始,从而使其可以用于各类程序中。

#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)
这个模块通过获取时间开始、调用函数固定的次数并且用开始时间减去停止时间,从而对任何位置和关键字参数调用任意函数进行计时。注意以下几点:
(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语句,用来终止生成器。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python中的迭代器和列表解析都是用于处理序列数据的工具,但它们的实现方式和用途有所不同。 迭代器是一个可以遍历序列数据的对象,它提供了__next__()方法来获取序列中的下一个元素,如果序列已经遍历完了,则会抛出StopIteration异常。迭代器可以用于处理大型的序列数据,因为它不需要一次性将所有数据放进内存中,而是逐个获取需要的元素。以下是一个简单的迭代器示例: ```python class MyIterator: def __init__(self, data): self.index = 0 self.data = data def __iter__(self): return self def __next__(self): if self.index >= len(self.data): raise StopIteration result = self.data[self.index] self.index += 1 return result # 使用迭代器遍历列表 my_list = [1, 2, 3] my_iterator = MyIterator(my_list) for item in my_iterator: print(item) ``` 列表解析是一种简洁的语法,用于快速生成新的列表。它使用类似于for循环的语法来遍历原始列表,并使用条件表达式来过滤和转换列表中的元素。以下是一个简单的列表解析示例: ```python # 使用列表解析生成新的列表 my_list = [1, 2, 3] new_list = [x**2 for x in my_list if x > 1] print(new_list) # 输出 [4, 9] ``` 在上面的代码中,我们使用列表解析遍历原始列表my_list,并使用条件表达式过滤出大于1的元素,并将它们的平方放入新的列表new_list中。列表解析更适合用于简单的转换和过滤操作,而不是复杂的迭代操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值