python学习笔记 07 常用高阶函数

Posted by Huper on December 23, 2017

python比较好的一点就是它引进了一些函数式编程特性,比如高阶函数嵌套函数匿名函数等等,虽然算不上地道的函数式编程,但是这些特性的确非常方便,对于提高代码简洁程度也有很大帮助。今天来学习python里几个有用的高阶函数高阶函数简单说就是支持函数作为参数以及返回值,也可以用label去引用一个函数。

map/reduce

这里的map/reduce借鉴了Googlemap/reduce思想,实现了一种便捷的映射+规约的组合数据处理方式。但是Google的map/reduce远比这里要讲的复杂,它主要实现将数据分发进行分布式处理,再将结果整合,涉及很多分布式和并行算法的理论。 python里的map操作是把某个映射函数作用到一些列数据上,使用方式如下:

>>> def func(x):
...     return x * x
...
>>> r = map(func,[1,2,3,4,5])
>>> r
<map object at 0x00000154D5E9A550>
>>> list(r)
[1, 4, 9, 16, 25]

可以看到,map函数可以接受一个函数作为映射规则,还可以接受一个影射对象。使用map函数会得到一个map对象,可以使用list()方法获得相应的列表对象。我们也可以使用内置的工厂函数作为映射规则,比如如下的代码就是实现了将列表转换为字符列表:

>>> list(map(str,[1,2,3,4,5]))
['1', '2', '3', '4', '5']

再来看下reduce函数,它的功能是将一个序列里的对象按照某种规则两两规约。也需要接受两个参数,一个是规约规则,另一个是规约数据:

>>> from functools import reduce
>>> def add(x,y):
...     return x+y
...
>>> reduce(add, [1,2,3,4,5])
15

map不同的是reduce函数在functools模块里,需要单独导入。事实上:

reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

reduce函数的序列部分还可以接受一个map对象作为参数,这两个东西搭配lambda表达式使用会将代码精简度提升到极致,比如说我们要计算某个字符串ASSIC码的总和:

>>> reduce(lambda x,y : x + y, list(map(ord, '123456')))
309
# 错误写法:
>>> reduce(sum, map(ord, '123456'))
# 上述方法的改进:
>>> reduce(lambda x,y : x + y, map(ord, '123456'))
309

是不是发现了一个玄机?我们用自己写的函数作用于reduce是不要求对象可迭代的,但是sum这个函数作用于reduce就必须先将数据转换成可迭代对象。python里获取字符ASSIC码的操作有点不一样,需要使用ord函数,另外这里补充一下python和java都是强类型检查语言,不能容忍跨类别赋值和操作,python里的类型检查更强一点,因为它本身也是动态类型检查语言,就算遇到+这样的运算符,也是去检查对象的__add____radd__方法,类似java里在进行字符串相加时会检查对象的tostring方法。

filter

顾名思义,这是个过滤器,根据指定的过滤规则,过滤指定的数据。过滤规则也是个函数,使用方式如下:

>>> def is_odd(n):
...     return n % 2 == 1
...
>>> ans = filter(is_odd, [1,2,3,4,5,6,7,8,9])
>>> ans
<filter object at 0x00000154D5E9A470>
>>> list(ans)
[1, 3, 5, 7, 9]

可以看到,类似map,这里返回了一个filter对象,并且可以使用list将其转换成列表对象。我们可以用filter配合生成器非常轻松的实现素数筛的算法:

>>> def _odd_gen():
...     n = 1
...     while True:
...     n += 2
  File "<stdin>", line 4
    n += 2
    ^
IndentationError: expected an indented block
>>> def _odd_gen():
...     n = 1
...     while True:
...             n += 2
...             yield n
...
# lambda实现的嵌套函数,用到了闭包
>>> def _not_divisible(n):
...     return lambda x:x % n > 0
...
>>> def primes():
...     yield 2
...     it = _odd_gen()
...     while True:
...             yield n = next(it)
>>> def primes():
...     yield 2
...     it = _odd_gen()
...     while True:
...             n = next(it)
...             yield n
...             it = filter(_not_divisible(n),it)
...
>>> for n in primes():
...     if n < 1000:
...             pass
...     else:
...             print('finished!')
...             break
...
finished!

sorted

java里排序有很多方式,可以继承Compareable接口,也可以在使用Collections.sort方法并指定一个Comparator(可以使用匿名内部类)。python里的排序一般用到sorted函数:

>>> sorted([1,5,5,8,6,1564,4])
[1, 4, 5, 5, 6, 8, 1564]

sorted也是个高阶函数,可以指定一个函数作为它的排序规则,但是要使用参数名key显示指定,比如:

>>> sorted([1,3,-6,-2,5], key = abs)
[1, -2, 3, 5, -6]

想要定制化的排序?如下:

# 使用匿名函数指定key
>>> sorted([(2,'123'),(1,'12'),(3,'456789'),(4,'12')], key = lambda x: len(x[1]))
[(1, '12'), (4, '12'), (2, '123'), (3, '456789')]
# 指定两个key,第一个相同时,看第二个
>>> sorted([(2,'123'),(1,'12'),(3,'456789'),(4,'12')], key = lambda x: (len(x[1]),x[0]))
[(1, '12'), (4, '12'), (2, '123'), (3, '456789')]

一个有趣的问题:第一个key升序,第二个key降序怎么办?不嫌麻烦的话可以排序两次;python2的话可以使用cmp参数,但是python3里的sorted取消了类似C++里的cmp 参数,这时可以使用functools模块的cmp_to_key函数将一个cmp转换成key再使用sorted;如果是需要降序的key是数字的话,可以直接使用:

>>> sorted([(2,'123'),(1,'12'),(3,'456789'),(4,'12')], key = lambda x: (len(x[1]),-x[0]))
[(4, '12'), (1, '12'), (2, '123'), (3, '456789')]