这几个Python内置的高阶函数,真香

Python七号

共 6803字,需浏览 14分钟

 ·

2020-07-28 17:47

阅读本文大概需要 6 分钟。

奇怪,reduce去哪了?

什么是高阶函数?,一句话,就是可以接受其他函数名称作为自己参数的函数。函数式编程说的就是这个。Python中一切皆对象,函数也是一个对象,可以作为变量名称传递给其他函数调用,高阶函数就是一种特殊的函数,有 5 个内置的函数可以大大提高我们的编程效率,分别是 sorted、filter、zip、map、reduce,这里除了 zip 函数,其他都是高阶函数。它们的用武之地非常广泛,要不也不会作为内置函数了。今天分享下它们的用法,掌握之后,你一定会觉得,真香!

1、sorted 函数

函数原型:sorted(iterable, *, key=None, reverse=False)
首先,它是一个稳定的排序算法,接收一个可迭代对象,两个必须通过关键字传参的可选参数,返回一个排序后的可迭代对象。key 是用来指定按照那个信息进行比较排序的函数,比如 key = str.lower,如果不指定,则默认按照可迭代对象中的元素进行比较。
基本用法:
>>> v_list = [5,2,3,4,1]
>>> sorted(v_list)
[12345]
>>> v_tuple = (5,2,3,4,1)
>>> sorted(v_tuple)
[12345]
>>> v_dict = {5:'a',2:'b',3:'c',4:'d',1:'e'}
>>> sorted(v_dict)
[12345]
>>>
可以看出,只要是可迭代对象,都可以使用 sorted。
进阶用法,指定关键字进行排序:
>>> v_dict = {5:'a',2:'b',3:'c',4:'d',1:'e'}
>>> sorted(v_dict,key=lambda x:v_dict[x])
[52341]
>>> student_tuples = [
...     ('john''A'15),
...     ('jane''B'12),
...     ('dave''B'10),
... ]
>>> sorted(student_tuples, key=lambda student: student[2])   # sort by age
[('dave''B'10), ('jane''B'12), ('john''A'15)]
还可以对对象进行排序,代码如下:
class Student:
    def __init__(self, name, grade, age):
        self.name = name
        self.grade = grade
        self.age = age
    def __repr__(self):
        return repr((self.name, self.grade, self.age))

student_objects = [
    Student('john''A'15),
    Student('jane''B'12),
    Student('dave''B'10),
]
sorted(student_objects, key=lambda student: student.age)   # sort by age

#[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
上述指定 key 的用法非常普遍,python 还提供了非常便利的访问器 operator, operator 模块有 itemgetter() 、 attrgetter() 和 methodcaller() 函数。用法也简单易学,如下:
itemgetter 指定按待排序元素指定位置的数据进行排序:
>>> student_tuples
[('john''A'15), ('jane''B'12), ('dave''B'10)]
>>> from operator import itemgetter, attrgetter
>>> sorted(student_tuples, key=itemgetter(2))
[('dave''B'10), ('jane''B'12), ('john''A'15)]
如果要排序的是类,可以使用 attrgetter 指定按那个属性排序:
>>> class Student:
...     def __init__(self, name, grade, age):
...         self.name = name
...         self.grade = grade
...         self.age = age
...     def __repr__(self):
...         return repr((self.name, self.grade, self.age))
...
>>> student_objects = [
...     Student('john''A'15),
...     Student('jane''B'12),
...     Student('dave''B'10),
... ]
>>> student_objects
[('john''A'15), ('jane''B'12), ('dave''B'10)]
>>> sorted(student_objects, key=attrgetter('age'))
[('dave''B'10), ('jane''B'12), ('john''A'15)]
>>>
还可以指定多个关键字排序,比如先按照 grade 排序,再按照 age 排序:
>>> sorted(student_tuples, key=itemgetter(1,2))
[('john''A'15), ('dave''B'10), ('jane''B'12)]
>>> sorted(student_objects, key=attrgetter('grade''age'))
[('john''A'15), ('dave''B'10), ('jane''B'12)]
排序默认使用升序,如果要降序,传入一个关键字参数 reverse = True 即可。
>>> sorted(student_tuples, key=itemgetter(2), reverse=True)
[('john''A'15), ('jane''B'12), ('dave''B'10)]
排序是稳定的:
>>> data = [('red'1), ('blue'1), ('red'2), ('blue'2)]
>>> sorted(data, key=itemgetter(0))
[('blue'1), ('blue'2), ('red'1), ('red'2)]
排序算法使用 Timsort,Timsort 是一种混合稳定的排序算法,源自归并排序和插入排序,旨在较好地处理真实世界中各种各样的数据,从 2.3 版本起,Timsort 一直是 Python 的标准排序算法。它还被 Java SE7, Android platform, GNU Octave, 谷歌浏览器和 Swift 用于对非原始类型的数组排序。

2、filter 函数

函数原型:filter(function, iterable)
filter() 函数用于过滤一个可迭代对象,过滤掉不符合条件的元素,返回由符合条件元素组成的新的可迭代对象。
filter 接收两个参数,第一个为函数,第二个为可迭代对象,可迭代对象中的每个元素作为参数传递给函数进行判断,然后返回 True 或 False,最后将返回 True 的元素放到新可迭代对象中。
如获取列表中的偶数:
>>> v_list = [1,2,3,4,5,6]
>>> new = filter(lambda x: x%2 ==0, v_list)
>>> list(new)
[246]
>>> number_list = range(-55)
>>> less_than_zero = list(filter(lambda x: x < 0, number_list))
>>> less_than_zero
[-5-4-3-2-1]
filter 使用方法很简单,也很好理解,不多说。

3、zip 函数

函数原型:zip(*iterables)
提到 zip 你一定会想到压缩,不过这里表示的是一种重新组合的意思,看下面的代码就知道了:
>>> list(zip("abc","xy"))
[('a''x'), ('b''y')]
>>> list(zip("abc","xy",[1,2,3,4]))
[('a''x'1), ('b''y'2)]
函数接受不限数目的可迭代对象,按照个数最小的可迭代对象进行重新组合,组合的策略就是按照原有的顺序进行,第 i 个元组包含来自每个参数序列或可迭代对象的第 i 个元素。当所输入可迭代对象中最短的一个被耗尽时,迭代器将停止迭代。当只有一个可迭代对象参数时,它将返回一个单元组的迭代器。不带参数时,它将返回一个空迭代器。用代码来解释 zip 就是:
def zip(*iterables):
    # zip('ABCD', 'xy') --> Ax By
    sentinel = object()
    iterators = [iter(it) for it in iterables]
    while iterators:
        result = []
        for it in iterators:
            elem = next(it, sentinel)
            if elem is sentinel:
                return
            result.append(elem)
        yield tuple(result)
还有一种理解就是行转列,比如:
>>> x = [123]
>>> y = [456]
>>> zipped = zip(x, y)
>>> list(zipped)
[(14), (25), (36)]
如果是二维数,使用 zip 行转列就太方便了:
>>> array = [ [1,2,3,4],[5,6,7,8],[9,10,11,12]]
>>> list(zip(*array))
[(159), (2610), (3711), (4812)]
注意,zip 以最短的可迭代对象来进行组合,其他元素丢弃,整个过程并不报错,如果不希望丢弃元素,可以使用 itertools 中的 zip_longest 方法,如下:
>>> x = [1,2,3]
>>> y = [4,5]
>>> z = [6,7,8,9]
>>> list(zip(x,y,z))
[(146), (257)]
>>> from itertools import zip_longest
>>> list(zip_longest(x,y,z))
[(146), (257), (3None8), (NoneNone9)]

4、map/reduce 函数

函数原型:
  • map(function, iterable, …)

  • reduce(function, iterable[, initializer])

如果你读过 Google 的那篇大名鼎鼎的论文 “MapReduce: Simplified Data Processing on Large Clusters”,你就能大概明白 map/reduce 的概念。
简单来说,map 就是分发任务,reduce 就是对结果进行汇总。Python 内置的高阶函数 map/reduce 也是这个理儿。
比如,要对列表中的每个元素执行特定的任务,如果列表元素个数是 10 个,就要调用 10 次,有了 map 一行代码搞定:
>>> def fun(x):
...     return x*x
...
>>> v_list = [1,2,3,4,5,6,7,8,9,10]
>>> map(fun,v_list)
0x10ff240f0>
>>> list(map(fun,v_list))
[149162536496481100]
map() 传入的第一个参数是 fun,即函数对象本身。由于 map object 是一个 Iterator,Iterator 是惰性序列,因此通过 list() 函数让它把整个序列都计算出来并返回一个 list。有人说我不用 map,写个循环也可以搞定,没错,但那样可读性就变差了,下面的代码,你能一眼看出来 把 fun(x) 作用在 list 的每一个元素并把结果生成一个新的 list:
L = []
for n in [123456789,10]:
    L.append(fun(n))
print(L)
再看 reduce 的用法。reduce 把一个函数作用在一个可迭代对象[x1, x2, x3, …]上,第一个对象的结果作为参数传递给下一次调用,因此这个函数必须接收两个参数。
初学者可以简单的理解为累加、累积、就是前一步的结果是下一步的输入,举个例子:
>>> from functool import reduce
>>> def add(x,y):
...     return x+y
...
>>> reduce(add,[1,3,5,7])
16
Python3 中 reduce 被移到了 functools,因为 Guido 先生讨厌 reduce。
当然求和运算可以直接用 Python 内建函数 sum(),没必要动用 reduce。但是如果要把序列 [1, 3, 5, 7, 9] 变换成整数 13579,reduce 就可以派上用场:
>>> from functools import reduce
>>> def fn(x, y):
...     return x * 10 + y
...
>>> reduce(fn, [13579])
13579
这个例子本身没多大用处,但是,如果考虑到字符串 str 也是一个序列,对上面的例子稍加改动,配合map(),我们就可以写出把 str 转换为 int 的函数:
from functools import reduce

DIGITS = {'0'0'1'1'2'2'3'3'4'4'5'5'6'6'7'7'8'8'9'9}

def char2num(s):
    return DIGITS[s]

def str2int(s):
    return reduce(lambda x, y: x * 10 + y, map(char2num, s))
也就是说,即使 Python 不提供 int() 函数,完全可以自己写一个把字符串转化为整数的函数,而且只需要几行代码!
(完)
参考文档:
  • Python 官方文档:https://docs.python.org/zh-tw/3/library/functions.html

  • map/reduce:https://www.liaoxuefeng.com/wiki/1016959663602400/1017329367486080

  • Timsort:https://zh.wikipedia.org/wiki/Timsort

推荐阅读:
python 基础系列--可迭代对象、迭代器与生成器
深入理解迭代器和生成器
浏览 16
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报