Python 函数式编程
高阶函数
变量可以指向函数
以Python内置的求绝对值的函数abs()
为例:
>>> abs(-10) 10 >>> abs <built-in function abs>
可见,abs(-10)
是函数调用,而abs
是函数本身。
如果把函数本身赋值给变量:
>>> f = abs >>> f(-10) 10
成功!说明变量f
现在已经指向了abs
函数本身。直接调用abs()
函数和调用变量f()
完全相同。
函数名也是变量
函数名其实就是指向函数的变量!对于abs()
这个函数,完全可以把函数名abs
看成变量,它指向一个可以计算绝对值的函数!
把abs
指向10
后,就无法通过abs(-10)
调用该函数了!因为abs
这个变量已经不指向求绝对值函数而是指向一个整数10
!
传入函数
既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
def add(x, y, f): return f(x) + f(y)
map/reduce
map()
函数接收两个参数,一个是函数,一个是Iterable
,map
将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator
返回。
>>> def f(x): ... return x * x ... >>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9]) >>> list(r) [1, 4, 9, 16, 25, 36, 49, 64, 81]
map()
传入的第一个参数是f
,即函数对象本身。由于结果r
是一个Iterator
,Iterator
是惰性序列,因此通过list()
函数让它把整个序列都计算出来并返回一个list。
reduce
把一个函数作用在一个序列[x1, x2, x3, ...]
上,这个函数必须接收两个参数,reduce
把结果继续和序列的下一个元素做累积计算,其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
把序列[1, 3, 5, 7, 9]
变换成整数13579
:
>>> from functools import reduce >>> def fn(x, y): ... return x * 10 + y ... >>> reduce(fn, [1, 3, 5, 7, 9]) 13579
filter
filter()
函数用于过滤序列。
和map()
类似,filter()
也接收一个函数和一个序列。和map()
不同的是,filter()
把传入的函数依次作用于每个元素,然后根据返回值是True
还是False
决定保留还是丢弃该元素。
把一个序列中的空字符串删掉,可以这么写:
def not_empty(s): return s and s.strip() list(filter(not_empty, ['A', '', 'B', None, 'C', ' '])) # 结果: ['A', 'B', 'C']
注意到filter()
函数返回的是一个Iterator
,也就是一个惰性序列,所以要强迫filter()
完成计算结果,需要用list()
函数获得所有结果并返回list。
求素数:
#!/usr/bin/env python3 # -*- coding: utf-8 -*- def main(): for n in primes(): if n < 1000: print(n) else: break def _odd_iter(): n = 1 while True: n = n + 2 yield n def _not_divisible(n): return lambda x: x % n > 0 def primes(): yield 2 it = _odd_iter() while True: n = next(it) yield n it = filter(_not_divisible(n), it) if __name__ == '__main__': main()
求回数:
def is_palindrome(n): n = str(n) return n[:] == n[::-1] output = filter(is_palindrome, range(1000)) print(list(output))
sorted
Python内置的sorted()
函数就可以对list进行排序。
sorted()
函数也是一个高阶函数,它还可以接收一个key
函数来实现自定义的排序,例如按绝对值大小排序:
>>> sorted([36, 5, -12, 9, -21], key=abs) [5, 9, -12, -21, 36]
key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序。
要进行反向排序,不必改动key函数,可以传入第三个参数reverse=True
:
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True) ['Zoo', 'Credit', 'bob', 'about']
用一组tuple表示学生名字和成绩,用sorted()
对列表分别按名字排序和按成绩从高到低排序:
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)] def by_name(t): return t[0].lower() def by_score(t): return t[1] L2 = sorted(L, key=by_name) L3 = sorted(L, key = by_score, reverse = True) print(L2) print(L3)
返回函数
函数作为返回值
用一个求和函数为例:
def lazy_sum(*args): def sum(): ax = 0 for n in args: ax = ax + n return ax return sum
当我们调用lazy_sum()
时,返回的并不是求和结果,而是求和函数:
>>> f = lazy_sum(1, 3, 5, 7, 9) >>> f <function lazy_sum.<locals>.sum at 0x101c6ed90>
调用函数f
时,才真正计算求和的结果:
>>> f() 25
在函数lazy_sum
中又定义了函数sum
,并且,内部函数sum
可以引用外部函数lazy_sum
的参数和局部变量,当lazy_sum
返回函数sum
时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。
注意一点,当我们调用lazy_sum()
时,每次调用都会返回一个新的函数,即使传入相同的参数:
>>> f1 = lazy_sum(1, 3, 5, 7, 9) >>> f2 = lazy_sum(1, 3, 5, 7, 9) >>> f1==f2 False
f1()
和f2()
的调用结果互不影响。
匿名函数
以map()
函数为例,计算f(x)=x2时,除了定义一个f(x)
的函数外,还可以直接传入匿名函数:
>>> list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])) [1, 4, 9, 16, 25, 36, 49, 64, 81]
关键字lambda
表示匿名函数,冒号前面的x
表示函数参数。
匿名函数有个限制,就是只能有一个表达式,不用写return
,返回值就是该表达式的结果。
匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:
>>> f = lambda x: x * x >>> f <function <lambda> at 0x101c6ef28> >>> f(5) 25
同样,也可以把匿名函数作为返回值返回,比如:
def build(x, y): return lambda: x * x + y * y
偏函数
int()
函数可以把字符串转换为整数,当仅传入字符串时,int()
函数默认按十进制转换,但它
还提供额外的base
参数,如果传入
base
参数,就可以做N进制的转换:
>>> int('12345', base=8) 5349 >>> int('12345', 16) 74565
假设要转换大量的二进制字符串,每次都传入int(x, base=2)
非常麻烦,于是,我们想到,可以定义一个int2()
的函数,默认把base=2
传进去:
def int2(x, base=2): return int(x, base)
functools.partial
就是帮助我们创建一个偏函数的,不需要我们自己定义int2()
,可以直接使用下面的代码创建一个新的函数int2
:
>>> import functools >>> int2 = functools.partial(int, base=2) >>> int2('1000000') 64
简单总结functools.partial
的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。