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

2020-11-25 14:32:43 浏览数 (1)

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

奇怪,reduce去哪了?

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

1、sorted 函数

函数原型:sorted(iterable, *, key=None, reverse=False)

首先,它是一个稳定的排序算法,接收一个可迭代对象,两个必须通过关键字传参的可选参数,返回一个排序后的可迭代对象。key 是用来指定按照那个信息进行比较排序的函数,比如 key = str.lower,如果不指定,则默认按照可迭代对象中的元素进行比较。

基本用法:

代码语言:javascript复制
>>> v_list = [5,2,3,4,1]
>>> sorted(v_list)
[1, 2, 3, 4, 5]
>>> v_tuple = (5,2,3,4,1)
>>> sorted(v_tuple)
[1, 2, 3, 4, 5]
>>> v_dict = {5:'a',2:'b',3:'c',4:'d',1:'e'}
>>> sorted(v_dict)
[1, 2, 3, 4, 5]
>>>

可以看出,只要是可迭代对象,都可以使用 sorted。

进阶用法,指定关键字进行排序:

代码语言:javascript复制
>>> v_dict = {5:'a',2:'b',3:'c',4:'d',1:'e'}
>>> sorted(v_dict,key=lambda x:v_dict[x])
[5, 2, 3, 4, 1]
>>> 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)]

还可以对对象进行排序,代码如下:

代码语言:javascript复制
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 指定按待排序元素指定位置的数据进行排序:

代码语言:javascript复制
>>> 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 指定按那个属性排序:

代码语言:javascript复制
>>> 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 排序:

代码语言:javascript复制
>>> 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 即可。

代码语言:javascript复制
>>> sorted(student_tuples, key=itemgetter(2), reverse=True)
[('john', 'A', 15), ('jane', 'B', 12), ('dave', 'B', 10)]

排序是稳定的:

代码语言:javascript复制
>>> 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 的元素放到新可迭代对象中。

如获取列表中的偶数:

代码语言:javascript复制
>>> v_list = [1,2,3,4,5,6]
>>> new = filter(lambda x: x%2 ==0, v_list)
>>> list(new)
[2, 4, 6]
>>> number_list = range(-5, 5)
>>> 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 你一定会想到压缩,不过这里表示的是一种重新组合的意思,看下面的代码就知道了:

代码语言:javascript复制
>>> 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 就是:

代码语言:javascript复制
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)

还有一种理解就是行转列,比如:

代码语言:javascript复制
>>> x = [1, 2, 3]
>>> y = [4, 5, 6]
>>> zipped = zip(x, y)
>>> list(zipped)
[(1, 4), (2, 5), (3, 6)]

如果是二维数,使用 zip 行转列就太方便了:

代码语言:javascript复制
>>> array = [ [1,2,3,4],[5,6,7,8],[9,10,11,12]]
>>> list(zip(*array))
[(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)]

注意,zip 以最短的可迭代对象来进行组合,其他元素丢弃,整个过程并不报错,如果不希望丢弃元素,可以使用 itertools 中的 zip_longest 方法,如下:

代码语言:javascript复制
>>> x = [1,2,3]
>>> y = [4,5]
>>> z = [6,7,8,9]
>>> list(zip(x,y,z))
[(1, 4, 6), (2, 5, 7)]
>>> from itertools import zip_longest
>>> list(zip_longest(x,y,z))
[(1, 4, 6), (2, 5, 7), (3, None, 8), (None, None, 9)]

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 一行代码搞定:

代码语言:javascript复制
>>> def fun(x):
...     return x*x
...
>>> v_list = [1,2,3,4,5,6,7,8,9,10]
>>> map(fun,v_list)
<map object at 0x10ff240f0>
>>> list(map(fun,v_list))
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

map() 传入的第一个参数是 fun,即函数对象本身。由于 map object 是一个 Iterator,Iterator 是惰性序列,因此通过 list() 函数让它把整个序列都计算出来并返回一个 list。有人说我不用 map,写个循环也可以搞定,没错,但那样可读性就变差了,下面的代码,你能一眼看出来 把 fun(x) 作用在 list 的每一个元素并把结果生成一个新的 list:

代码语言:javascript复制
L = []
for n in [1, 2, 3, 4, 5, 6, 7, 8, 9,10]:
    L.append(fun(n))
print(L)

再看 reduce 的用法。reduce 把一个函数作用在一个可迭代对象[x1, x2, x3, …]上,第一个对象的结果作为参数传递给下一次调用,因此这个函数必须接收两个参数。

初学者可以简单的理解为累加、累积、就是前一步的结果是下一步的输入,举个例子:

代码语言:javascript复制
>>> 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 就可以派上用场:

代码语言:javascript复制
>>> from functools import reduce
>>> def fn(x, y):
...     return x * 10   y
...
>>> reduce(fn, [1, 3, 5, 7, 9])
13579

这个例子本身没多大用处,但是,如果考虑到字符串 str 也是一个序列,对上面的例子稍加改动,配合map(),我们就可以写出把 str 转换为 int 的函数:

代码语言:javascript复制
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() 函数,完全可以自己写一个把字符串转化为整数的函数,而且只需要几行代码!

(完)

0 人点赞