高阶函数
下面介绍一下函数的高级用法,包括Map/Reduce
、filter
、sorted
、lambda
、decorator
、偏函数
等
函数特性
首先介绍一下函数的特性,比如之前所学过的函数可以赋值给变量,以及函数如何作为参数传个其他函数。
函数名也是变量
函数名也是变量,所以在定义变量名时要注意,不要使用系统函数命名变量,如下所示:
>>> abs = 10
>>> abs(-10)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable
abs
被定义成了整数类型,所以在调用abs
时会报错。
注:由于abs
函数实际上的定义在import builtins
模块中的,所以要让修改abs
变量的指向在其他模块生效,要用import builtins;builtins.abs = 10
。
传入函数
函数既然是变量,那么函数也可以当作参数传给其他函数,那么这个接受函数参数的函数称为高阶函数
。
def add(x, y, f):
return f(x) + f(y)
Map/reduce
Python内建了map()
和reduce()
函数。
如果你读过Google的那篇大名鼎鼎的论文“MapReduce: Simplified Data Processing on Large Clusters”,你就能大概明白map/reduce
的概念。
map()
map()
函数接收两个参数,一个是函数
,一个是Iterable
,并对Iterable
参数的每一个元素执行函数
,并以Iterator
作为结果返回。
举例说明,比如我们有一个函数f(x)=x^2
,要把这个函数作用在一个list [1, 2, 3, 4, 5, 6, 7, 8, 9]
上,就可以用map()
实现如下:
f(x) = x * x
│
│
┌───┬───┬───┬───┼───┬───┬───┬───┐
│ │ │ │ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼
[ 1 2 3 4 5 6 7 8 9 ]
│ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │
▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼
[ 1 4 9 16 25 36 49 64 81 ]
Python代码实现如下:
>>> 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]
reduce()
再看reduce
的用法。reduce
把一个函数作用在一个序列[x1, x2, x3, ...]
上,这个函数必须接收两个参数,reduce
把结果继续和序列的下一个元素做累积计算,其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
比如使用map()
和reduce()
实现一个str2int()
函数:
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 str2int(s):
def fn(x, y):
return x * 10 + y
def char2num(s):
return DIGITS[s]
return reduce(fn, map(char2num, s))
还可以用lambda
函数进一步简化:
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))
lambda函数
后续介绍。
filter
filter()
函数用于过滤序列
,filer
也是接收一个函数
和一个序列
,通过函数作用于每个元素后返回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求素数,有兴趣的可以看一下。
sorted
sorted()
函数可以对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]
key
函数将对每个元素执行,然后sorted()
再根据key
的返回结果排序。sorted
默认返回的是升序,也可以通过传入插入 reverse=True
来降序排列。
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
['Zoo', 'Credit', 'bob', 'about']
返回函数
高阶函数
除了可以接受函数作为参数外,还可以把函数作为结果值返回
。
我们来实现一个可变参数的求和。通常情况下,求和的函数是这样定义的:
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
当我们调用lazy_sum()
时,返回的并不是求和结果,而是求和函数:
>>> f = lazy_sum(1, 3, 5, 7, 9)
>>> f
<function lazy_sum.<locals>.sum at 0x101c6ed90>
>>> f()
25
在函数lazy_sum
中定义了函数sum
,并且sum
可以引用lazy_sum
的参数和局部变量,外部函数
亦可以通过sum
访问lazy_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()的调用结果互不影响
闭包
下面我们看一个例子:
def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 = count()
你可能认为调用f1(),f2()和f3()
结果应该是1,4,9,但实际结果是:
>>> f1()
9
>>> f2()
9
>>> f3()
9
原因就在于返回的函数引用了变量i
,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i
已经变成了3,因此最终结果为9。
可以通过下面方式避免这种情况:
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
匿名函数lambda
关键字lambda
表示匿名函数,冒号前面的x表示函数参数
,冒号后面为返回值
。如:lambda x: x * x
,
匿名函数有个限制,就是只能有一个表达式
。
装饰器
函数now(),定义如下:
def now():
print('2015-3-25')
现在要增强now()
函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()
函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)
。本质上,decorator
就是一个返回函数的高阶函数
。所以,我们要定义一个能打印日志的decorator
,可以定义如下:
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
观察上面的log
,因为它是一个decorator
,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法
,把decorator
置于函数的定义处:
@log
def now():
print('2015-3-25')
这样在调用now()
时,就会自动打印日志了。
>>> now()
call now():
2015-3-25
把@log
放到now()
函数的定义处,相当于执行了语句:
now = log(now)
关于decorator
更详细内容参见廖雪峰的官方网站
偏函数
Python的functools
模块提供了很多有用的功能,其中一个就是偏函数
(Partial function)。
举例如下:int()
函数可以把字符串转换为整数,当仅传入字符串时,int()
函数默认按十进制
转换:
>>> int('12345')
12345
但int()
函数还提供额外的base
参数,默认值为10。如果传入base
参数,就可以做N进制
的转换:
如果希望将默认值改为二进制
,可以定义一个int2()
的函数,如下:
def int2(x, base=2):
return int(x, base)
当然我们可以用functools.partial
更好的实现int2
:
>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85
新的int2
函数,仅仅是把base参数
的默认值设为2,但在函数调用时也可以传入其他值:
>>> int2('1000000', base=10)
1000000
创建偏函数
时,实际上可以接收函数对象
、*args
和**kw
这3个参数,上面的int2
在调用时相当于执行了如下语句:
kw = { 'base': 2 }
int('10010', **kw)
再比如如下定义max2
:
max2 = functools.partial(max, 10)
实际上会把10
作为*args
的一部分自动加到左边,也就是:
max2(5, 6, 7)
相当于:
args = (10, 5, 6, 7)
max(*args)
结果为10。
学习资料源自廖雪峰的官方网站