高阶函数
定义:把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式.
理解:个人以为这种思想和逻辑思路,在C语言上早就有,C中的指向函数指针的指针这种思想概念是可以的,而OC中却没有强化这种概念,OC的Block已经强大到足够完成这种纬度的调用和处理业务的场景复杂度,JavaScript这种脚本语言和Python解释型语言反而愈加强化这种观念,将函数(本质上是指针)作为参数使用传递.
变量可以指向函数
>>> x = abs(-10)
>>> x
10
abs是函数,abs(-10)是函数的调用,上面是将abs(-10)的结果赋值给x;
>>> f = abs
>>> f
<built-in function abs>
上面是将abs函数整体赋值给f,也就是函数本身赋值给变量,即:变量可以指向函数.
当一个变量指向了一个函数,那么可以通过该变量来调用这个函数;
>>> f = abs
>>> f(-10)
10
直接调用abs()函数和调用变量f()完全相同;
函数名也是变量
那么函数名是什么呢?函数名其实就是指向函数的变量!对于abs()这个函数,完全可以把函数名abs看成变量,它指向一个可以计算绝对值的函数!
传入函数
既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数.
def add(x, y, f):
return f(x) + f(y)
当调用add(-5, 6, abs)时,推导计算过程为
x = -5
y = 6
f = abs
f(x) + f(y) ==> abs(-5) + abs(6) ==> 11
return 11
编写高阶函数,就是让函数的参数能够接收别的函数.
map/reduce
map
重点了解map()和reduce()两个函数;
对于map()而言:接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回;
举例:将函数f(x) = x ^ 2 ,要把这个函数作用在一个list [1, 2, 3, 4, 5, 6, 7, 8, 9]上.
>>> 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.
当然上面的过程和需求的作用结果,我们用循环遍历也可以做,比如讲循环遍历挨个平方,也能得到类似的结果,但是你需要看真个循环你才知道这个循环的作用是用来干嘛的,而map()的意义在于,它作为高阶函数,把具体的运算规则抽象化;
例如:要把list [1, 2, 3, 4, 5, 6, 7, 8, 9]所有元素转成字符
>>> list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
['1', '2', '3', '4', '5', '6', '7', '8', '9']
用map()去执行的话,相当的简洁明.
reduce
reduce把一个函数作用在一个序列[x1, x2, x3, …]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
求和运算可以直接用Python内建函数sum(),没必要动用reduce.
假如需要把 [1,3,5,7,9]变换成整数13579,reduce就可以施展拳脚了.
>>> 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决定保留还是丢弃该元素.
举例:删掉list里的偶数,保留基数
def is_odd(n):
return n % 2 == 1
list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))
# 结果: [1, 5, 9, 15]
filter()函数返回的是一个Iterator,也就是一个惰性序列,所以要强迫filter()完成计算结果,需要用list()函数获得所有结果并返回list.
经典案例分析:用filter求素数
(质数又称素数,一个大于1的自然数,除了1和它自身外,不能被其他自然数整除的数叫做质数;否则称为合数.)
最常见的统计素数的方法是埃氏筛法,原理如下:
首先,列出从2开始的所有自然数,构造一个序列:
2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, …
取序列的第一个数2,它一定是素数,然后用2把序列的2的倍数筛掉:
3, 4(X), 5, 6(X), 7, 8(X), 9, 10(X), 11, 12(X), 13, 14(X), 15, 16(X), 17, 18(X), 19, 20(X), …
取新序列的第一个数3,它一定是素数,然后用3把序列的3的倍数筛掉:
5, 6(X), 7, 8(X), 9(X), 10(X), 11, 12(X), 13, 14(X), 15(X), 16(X), 17, 18(X), 19, 20(X), …
取新序列的第一个数5,然后用5把序列的5的倍数筛掉:
7, 8(X), 9(X), 10(X), 11, 12(X), 13, 14(X), 15(X), 16(X), 17, 18(X), 19, 20(X), …
不断筛下去,就可以得到所有的素数.
def _int_iter():
n = 1
while True:
n = n + 1
yield n
def _not_divisible(n):
return lambda x:x % n > 0
def primes():
it = _int_iter()
while True:
n = next(it)
yield n
it = filter(_not_divisible(n), it)
sorted
排序不管是常见的冒泡还是快排,核心的思想都是比较两个元素的大小;有个问题是:当我们比较数字大小的时候,可以直接快速的比较,但是对于比较字符串或者dict的时候,便没有了数学意义上的大小意义,因此可以把过程抽象出来.
举例:对List排序
>>> sorted([36, 5, -12, 9, -21])
[-21, -12, 5, 9, 36]
sorted()可以接收一个key函数来实现自定义的排序,比如按绝对值大小排序:
>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]
从这里可以看出,sorted()排序的关键在于实现一个映射函数.
返回函数
函数作为返回值
高阶函数不仅可以把函数作为参数,同时也可以把函数作为结果返回.
下面是个求和函数
def calc_sum(*args):
ax = 0
for n in args:
ax = ax + n
return ax
如果不需要立即求和,后续根据需求再求和,可以不返回求和结果,而是返回求和的函数:
def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum
(这两个是python中的可变参数.*args表示任何多个无名参数,它是一个tuple;**kwargs表示关键字参数,它是一个dict.并且同时使用*args和**kwargs时,必须*args参数列要在**\kwargs前.)
>>> f = lazy_sum(1,3,5,7,9)
>>> f
<function sum at 0x1010f9d70>
>>> f()
25
在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”.
闭包
返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量.
如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变.
def count():
def f(j):
def g():
return j*j
return g
fs = []
for i in range(1, 4):
fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f()
return fs
一个函数可以返回一个计算结果,也可以返回一个函数.
返回一个函数时,函数并未执行,返回函数中不要引用任何可能会变化的变量.
匿名函数
传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便.
Python中对匿名函数有一些有限的支持.
>>> 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: x * x的本质是
def f(x):
return x * x
关键字lambda [‘læmdə] (是λ的英文)表示匿名函数,冒号前面的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
装饰器
举例:
>>> def now():
... print('2015-3-25')
...
>>> f = now
>>> f()
2015-3-25
函数对象有一个name属性,可以拿到函数的名字:
>>> now.__name__
'now'
>>> f.__name__
'now'
要增强now()函数的功能,比如在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator).本质上,decorator就是一个返回函数的高阶函数.
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
log接受一个函数,且返回一个函数,Python中提供了一个@语法,可以把修饰器放置在函数定义的地方,从而来影响函数的执行效果和结果.
@log
def now():
print('2015-3-25')
这样你在调用now的时候,不仅会执行原本的now函数,同时会执行log函数,把@log放到now()函数的定义处,相当于执行了语句:
now = log(now)
由于log()是一个decorator,返回一个函数,所以原来的now()函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数.
wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用.在wrapper()函数内,首先打印日志,再紧接着调用原始函数.
def log(text):
def decorator(func):
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数.
调用如下:
@log('execute')
def now():
print('2015-3-25')
执行效果如下:
>>> now()
execute now():
2015-3-25
本质上调用了如下代码:
>>> now = log('execute')(now)
首先执行log(‘execute’),返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数
偏函数
当函数的参数个数太多,需要简化时,使用functools.partial可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单.