python 的内置函数map
可以对一个迭代器/序列中的每个元素进行操作,然后返回一个被操作后的迭代器对象。
* map
函数默认是c实现的。map
函数非常方便,类似于map
的概念在Java8中也有使用到。以及java中的著名开源框架rxjava
也有大量使用到这个概念。
- 如何在
python
中模拟map
的实现呢?
def mymap(func, *seqs):
res = []
# *seqs 相当于拆解成 list1,list2,...,listN
for args in zip(*seqs):
# zip 之后, args 其实是一个元组
print(args)
# *args 相当于拆解成 e1,e2,...,eN ,也就是 func(e1,e2,...,eN)
res.append(func(*args))
return res
这段代码实际来自 《learning python》
- 实话说,这段代码不好理解,特别是在没有添加任何注释的情况下。即使你已经知道了python中的参数魔法,你也不见得就能理解
mymap()
这个函数.
这里,我来一句一句剖析这段代码:
首先是第一句
def mymap(func,*seqs):
这句代码表示:调用时肯定要传递至少一个参数进来。对应的是
func
参数。从参数名来看,应该传递一个函数进来。
第二个参数*seqs
是可变参数。传进来后,会被封包成一个元组。
也就是说,假设*seqs
,你传递进来的是1,2,3,4,5
,那么,就会在mymap
中变成(1,2,3,4,5)
这样的一个元组。
当然,事实上,*seqs
对应的应该是多个序列或者多个迭代器,或者两者混合。比如传递进来的是[1,2,3],[4,5,6]
.那么到mymap()
内部就会被封包成([1,2,3],[4,5,6])
这样的一个元组。- 有趣的是,如果混合的话:假设传递进来是
[1,2,3],range(4,7)
,就会被封包成([1,2,3],range(4,7))
- 有趣的是,如果混合的话:假设传递进来是
然后是第二句:
res = []
,这一句不过是定义了一个空的列表。接着是第三句
for args in zip(*seqs):
注意:这一句首先是做了一个
zip
操作。而zip
的参数是*seqs
,这里的*seqs
是对元组的一个拆包。假如上面传递的参数为[1,2,3],[4,5,6]
,那么seqs == ([1,2,3],[4,5,6])
,而在传递给zip
的时候,又加了一个*
操作符。于是进行了拆包操作。也就是,相当于传递给zip
的参数为:[1,2,3],[4,5,6]
。相当于进行zip([1,2,3],[4,5,6])
——这里封了又拆,相当于没有封…
而zip
操作,又会返回一个迭代器,该迭代器其中的每个元素都是一个元组。也就是说,zip([1,2,3],[4,5,6])
之后,得到的迭代器里面的元素分别是(1,4),(2,5),(3,6)
这三个元素。于是这里的args
也分别赋值为这三个元素了。第四句:
print(args)
,这句也不过是打印每个元素罢了。不过通过打印也可以看到,这里的args
确实是一个个元组。第五句:
res.append(func(*args))
这里,外面是一个将元素添加进列表的操作。
里面是进行了一个func
操作。通过这里也可以看出func
必须是一个函数,而不是一个一般的对象。
func
里面的参数,又是一个带*
号的参数。这里的*
号,依然是一个拆包的操作。根据第三句的分析可知,args
是一个元组。在当前场景下,args
分别是:(1,4) , (2,5) , (3,6)
。那么,也就是,调用了3次func
函数。*
操作符,将元组拆包,于是,第一次调用,相当于:func(1,4)
,后面两次类似。
回到最外面的res.append()
。也就是,每次都将func(*args)
的结果收集起来了。第六句:
return res
。这一句就是返回之前收集的所有数据。
通过这简单的6句代码,就模拟了内置函数map
的操作。当然,这段代码,是有优化提升空间的。比如:中间的打印输出是不必要的;也不必要专门弄一个列表去收集数据,可以直接yield
每次结果。这样,得到的就是一个迭代器对象,而不是一个列表。
- 优化如下:
def mymap(func, *seqs):
for args in zip(*seqs):
yield func(*args)
优化后的代码,更符合内置
map
的行为。
- 这里随便使用一个
mymap()
函数:
def add(*args):
return sum(args)
rr = mymap(add, [1, 2, 3], range(4, 7))
print('rr = ', list(rr))
输出如下:
rr = [5, 7, 9]
ps: 这里的
add
函数,算是对内置sum
函数的一个强化。sum
只能接收可迭代对象。所以,不能进行sum(1,2,3,4,5,6)
这样的操作。而add
函数中,[自动]将*args
封包成一个元组了。也就是说,进行add(1,2,3,4,5,6)
,相当于进行sum((1,2,3,4,5,6))
。这样就可以利用sum
对可变参数之间求和了。~
- ok , 关于对内置函数
map
的模拟就进行到这里了。其实对map
的模拟本身并没有什么意义。因为模拟的map
运行速度必然比不上内置的map
(内置的map
是c实现的)。不过这个模拟,主要是对python 函数参数魔法的一次实践。 - 以上~
彩蛋时刻:
def mymap(func, *seqs):
return [func(*args) for args in zip(*seqs)]
def mymap(func, *seqs):
return (func(*args) for args in zip(*seqs))
个人以为彩蛋中的实现方式不够友好,可读性降低了。