④Python函数式编程
- 高阶函数
函数式编程的一个特点是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!
高阶函数
高阶函数有如下几个特征,逐步深入理解。
- 变量可以指向函数
如果把函数本身赋值给变量,即便相知相函数,那么可以通过变量来调用函数。
- 函数名也是变量
这里仅仅说明,函数名可以当作变量使用,但是实际编程过程中,基本不会将函数名当成变量来使用。
- 传入函数
变量可以指向函数,函数的参数可以接受变量,那么自然而然一个函数也能接受另一个函数作为参数,这种函数就称之为高阶函数。
上面定义的函数可以通过add(-5,6,abs)
这种方式调用。
map
接收两个参数,一个是函数,一个是Iterable
,map
将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator
返回。
举例说明:
map
返回的是惰性序列,因此通过list()
函数把整个序列都计算出来并返回一个list。
实际上map()
作为高阶函数,将运算规则抽象了,因此,我们不但可以计算简单的求平方的函数,还可以计算任意复杂的函数。例如将一个list所有数字都转化为字符串。
reduce
reduce
把一个函数作用在一个序列上,这个函数必须接受两个参数,reduce
将结果继续和序列的下一个元素做累计计算,其效果就是:
例如对一个序列进行求和就可以使用reduce
实现:
map与reduce结合使用
map可以与reduce实现更多的功能,甚至可以和lambda
表达式结合使用。
上面的代码就可以实现将字符串转化为整数的功能。
filter
Python内建的filter()
函数用于过滤序列。
和map()
函数类似,filter()
也接受一个函数和一个序列,但是和map()
不同的是,filter()
把传入的函数依次作用于每个元素,然后根据返回值是True
和False
决定保留还是丢弃该元素。
sorted
排序是程序中经常会使用的功能,无论使用冒泡排序还是快速排序,排序的核心是比较两个元素的大小。如果是数字,可以直接比较,但是如果是字符串和字典,直接比较数学上的大小是没有意义的,此时必须通过函数抽象出来。
大家所熟知的sorted()
函数其实也是个高阶函数,可以接受一个key
函数来实现自定义的排序。
key指定的函数会作用在list的每个元素上,并根据key函数返回的结果进行排序。
高阶函数的抽象能力非常强,而且,核心代码可以保持的非常简洁。
返回函数
函数作为返回值,高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。
可以通过返回凡是来定义一个求和函数:
当lazy_sum
函数返回函数sum
时,相关参数和变量都保存在返回的函数中,这种称为“闭包”(Closure)的程序结构拥有极大的威力。
lazy_sum
函数的每次调用都会返回一个新的函数,即使传入相同的参数。
- 闭包
返回闭包时注意一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
比如下面这种情况就是错误的:
本以为会返回1,4,9但是实际上会返回9,9,9。这是因为等到3个函数都返回时,他们所引用的变量i
已经变成3
,因此最终结果为9
。
为了解决这个问题,可以再创建一个函数,用该函数的参数绑定循环变量当前的值。
匿名函数
也就是常说的lambda
表达式,一定程度上,匿名函数可以简化代码的书写,但是也会增加代码的理解难度。
lambda
表示匿名函数。
匿名函数没有函数名,不需要担心函数冲突,同时匿名函数是一个函数对象,可以把匿名函数赋值给一个变量,再利用变量来调用该函数。
装饰器
由于函数也是一个对象,而且函数对象可以被赋给变量,所以,通过变量也能调用该函数。
函数对象有一个__name__
属性,可以拿到函数名字。
假设现在,要增强now()
函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()
函数定义,这种在代码运行期间动态增加功能的方法,称之为“装饰器”。
上面的log
,因为是一个decorator,所以接受一个函数作为参数,并返回一个函数。借助Python的@羽凡,把decorator置于函数的定义处:
把@log
放到now()
函数的定义处,相当于执行了语句:
由于log()
是一个decorator,返回一个函数,所以,原来的now()
函数仍然存在,只是现在同名的now
变量指向了新的函数,于是调用now()
将执行新函数,即在log()
函数中返回的wrapper()
函数。
wrapper()
函数的参数定义是(*args, **kw)
,因此,wrapper()
函数可以接受任意参数的调用。在wrapper()
函数内,首先打印日志,再紧接着调用原始函数。
如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更加复杂。
这个三层嵌套的decorator用法如下:
执行结果如下:
和两层嵌套的decorator相比,3层嵌套的效果是这样的:
我们来剖析上面的语句,首先执行log('execute')
,返回的是decorator
函数,再调用返回的函数,参数是now
函数,返回值最终是wrapper
函数。
以上两种decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有__name__
等属性,但你去看经过decorator装饰之后的函数,它们的__name__
已经从原来的'now'
变成了'wrapper'
:
因为返回的那个wrapper()
函数名字就是'wrapper'
,所以,需要把原始函数的__name__
等属性复制到wrapper()
函数中,否则,有些依赖函数签名的代码执行就会出错。
不需要编写wrapper.__name__ = func.__name__
这样的代码,Python内置的functools.wraps
就是干这个事的,所以,一个完整的decorator的写法如下:
或者针对带参数的decorator:
import functools
是导入functools
模块。模块的概念稍候讲解。现在,只需记住在定义wrapper()
的前面加上@functools.wraps(func)
即可。
偏函数
functolls.partial
就是帮助我们创建一个偏函数,不需要我们自己定义函数,可以直接使用用偏函数定义的函数。