高阶函数(High-order Function
)是函数式编程中非常重要的概念,它是提升代码抽象层次的重要方法和手段。越来越多的语言开始支持函数式编程的范式,比如Java、C++。
虽然Python不是像Haskell这样纯粹的函数式编程语言,但是它也具有函数式编程的一些特性;而且Python现在应用非常广泛,了解一些这方面的特性,可以帮助我们写出更加简洁的代码。
在介绍高阶函数之前,我们需要先弄清楚First Class
、Curry
、Closure
这几个概念。
Function Object
在Python中,有三种实现Function Object的形式:
-
def
关键字
def add(a, b):
return a + b
>>> add(2,3)
5
-
lambda
关键字
f = lambda a,b: a + b
>>> f(2,3)
5
- 实现了
__call__
函数的Class
class MyClass:
def __call__(self, a, b):
return a + b
>>> cls = MyClass()
>>> cls
<__main__.MyClass at 0x5e9d358>
>>> cls(2,3)
5
上述三种方式都可以实现一个add
的函数,并且三者在功能上是等价的。
由于函数是对象,所以,它可以被赋值给变量,可以被存储在数据结构中,可以作为函数的参数,还可以作为函数的返回值。
函数作为一等公民(First Class
)存在,是实现高阶函数(High-order function
)的基础。
Closure
在说闭包之前先来认识自由变量。在某个作用域中,如果使用了未在本作用域中声明的变量,对于此作用域来说,该变量就是一个自由变量。闭包(Closure),就是指引用了自由变量的函数。
以Python为例,可以这样来认识Closure
:
def inc(x):
def incx(y):
return x + y
return incx
>>> inc2 = inc(2)
>>> inc10 = inc(10)
>>> inc2(2)
4
>>> inc10(2)
12
在这个例子中,incx
函数绑定了一个自由变量x
。
Curry
在Haskell中,所有的函数都只有一个参数,所有的多参数的函数都是柯里函数。柯里函数不会一次性取完所有参数,而是在每次调用是只取用一个参数,并返回一个一元函数,来取下一个参数,以此类推。
以Haskell中的max
函数为例:
ghci> max 4 5
5
ghci> (max 4) 5
5
首先,max
会被应用到4
上,返回一个一元函数,随后以5
为参数调用返回的一元函数,并在这一步得到最终结果。因此,上述两个调用是等价的。
这样的好处是什么呢?简言之,我们只要以部分的参数来调用某函数,就可以得到一个部分应用(partial application)函数,部分应用函数所接受的参数的数量,和之前少传入的参数的数量一致。比如max 4
,可以得到一个一元函数。部分应用使得构造新函数变得便捷简便,随时都可以为传递为其它函数而构造出新函数。
在上面闭包的例子中,函数add(x,y)
有两个入参,我们把它改写成了两个具有一个入参的函数。下面两种使用方式是等效的:
>>> inc(2)(3)
5
>>> inc2(3)
5
高阶函数
什么是高阶函数:
- 是一个
Function Object
- 可以在函数中定义
- 可以赋值给变量
- 可以用作参数或返回值
map,reduce,filter
典型的高阶函数:map
,reduce
,filter
,也是几乎所有的函数式编程语言都会提供的函数,也是一种很重要的抽象手段。著名的MapReduce模型就来自于这个思想。
>>> sq = map( lambda x: x ** 2, range(0,10) )
>>> print([ i for i in sq ])
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
from functools import *
>>> sq = map( lambda x: x ** 2, range(0,10) )
>>> reduce(lambda x,y: x+y, sq)
285
>>> f = filter ( lambda x: x % 2 == 0, [ i for i in range(10) ])
>>> f
<filter at 0x50c56d8>
>>> print([ i for i in f ])
[0, 2, 4, 6, 8]
在Python的3.x版本中,map
、reduce
、filter
函数会构造出一个Iterator,而不是list。
Decorator
Decorator(装饰器)是高阶函数的语法糖,一般用@
符号标识。
def makeitalic(fn):
def wrapped(*args, **kwargs):
return "<i>" + fn(*args, **kwargs) + "</i>"
return wrapped
def makebold(fn):
def wrapped(*args, **kwargs):
return "<b>" + fn(*args, **kwargs) + "</b>"
return wrapped
@makeitalic
@makebold # hello = makeitalic(makebold(hello))
def hello(*args, **kwargs):
return "Hello. args: {}, kwargs: {}".format(args, kwargs)
>>> print( hello( 'hello', 'world', demo='generator'))
<i><b>Hello. args: ('hello', 'world'), kwargs: {'demo': 'generator'}</b></i>
在上面的这个示例中,分别定义了makeitalic
和makebold
两个decorator,对hello
函数进行相应的装饰。比较特别的是,decorator之间也可以进行组合,在上面这个示例中,它们组合起来完成了italic和bold的功能。从本质上可以理解为,这是一个多层嵌套的高阶函数。
此外,Decorator还可以带参数,可以是实现了__call__
的可调用对象,可以是带有不同参数的多个可调用对象的实例。
class BoldMaker:
def __init__(self, fn):
self.fn = fn
def __call__(self, *args, **kwargs):
return "<b>" + self.fn(args, kwargs) + "</b>"
@BoldMaker
def hello(*args, **kwargs):
return "Hello. args: {}, kwargs: {}".format(args, kwargs)
>>> print(hello("This is decorator of callable class"))
<b>Hello. args: (('This is decorator of callable class',), {}), kwargs: {}</b>
Decorator也可以带一个或者多个参数,例如:
def enclose_in_tags(opening_tag, closing_tag):
def make_with_tags(fn):
def wrapped():
return opening_tag + fn() + closing_tag
return wrapped
return make_with_tags
@enclose_in_tags(opening_tag='<h1>', closing_tag='</h1>')
def hello():
return "hello world"
>>> hello()
'<h1>hello world</h1>'
参考资料
谈谈闭包——以Swift为例
Using functional programming in Python like a boss: Generators, Iterators and Decorators
理解 Python 装饰器就看这一篇
Higher Order Functions: Using Filter, Map and Reduce for More Maintainable Code
网友评论