python:模拟内置函数map的实现

来源:互联网 发布:管家婆软件多少钱 编辑:程序博客网 时间:2024/06/07 05:22

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))

个人以为彩蛋中的实现方式不够友好,可读性降低了。

原创粉丝点击