Python5 函数式编程(Functional Programming)
高阶函数Higher-order function
变量可以指向函数
以abs()函数举例,把函数本身赋值给变量:
>>> f = abs >>> f <built-in function abs>
结论:函数本身也可以赋值给变量,即:变量可以指向函数。如果一个变量指向了一个函数,那么,可通过该变量来调用这个函数。直接调用abs()
函数和调用变量f()
完全相同。
函数名也是变量
对于abs()
这个函数,完全可以把函数名abs
看成变量,它指向一个可以计算绝对值的函数!
注:由于abs
函数实际上是定义在import builtins
模块中的,所以要让修改abs
变量的指向在其它模块也生效,要用import builtins; builtins.abs = ?
。
传入函数
既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。编写高阶函数,就是让函数的参数能够接收别的函数。
map/reduce
Python内建了map()
和reduce()
函数。
map()
函数接收两个参数,一个是函数,一个是Iterable
,map
将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator
返回。
reduce()函数
的用法是把一个函数作用在一个序列[x1, x2, x3, ...]
上,这个函数必须接收两个参数,reduce
把结果继续和序列的下一个元素做累积计算,其效果就是:
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
举例
利用map()
函数,把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字。输入:['adam', 'LISA', 'barT']
,输出:['Adam', 'Lisa', 'Bart']
:
from functools import reduce IN = ['adam','LISA','barT'] def standarize(IN): return IN[0].upper() + IN[1:].lower() print(list(map(standarize,IN)))
Python提供的sum()
函数可以接受一个list并求和,请编写一个prod()
函数,可以接受一个list并利用reduce()
求积:
def prod(x,y): return x*y print(reduce(prod,IN))
利用map
和reduce
编写一个str2float
函数,把字符串'123.456'
转换成浮点数123.456
:
from functools import reduce IN = '123.456' decimal = 0 def str2float(x,y): global decimal if y == '.': return x+0.0 if isinstance(x,int): return x*10+y
else: decimal = decimal + 1 return x+y/10**decimaldef char2num(L): return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9,'.':'.'}[L] print(reduce(str2float,map(char2num,IN)))
filter
Python内建的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()
这个高阶函数,关键在于正确实现一个“筛选”函数。
注意到filter()
函数返回的是一个Iterator
,也就是一个惰性序列,所以要强迫filter()
完成计算结果,需要用list()
函数获得所有结果并返回list。
用filter求素数
def main(): for n in primes(): if n < 100: 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)
sorted
排序算法
排序的核心是比较两个元素的大小。Python内置的sorted()
函数就可以对list进行排序。此外,sorted()
函数也是一个高阶函数,它还可以接收一个key
函数来实现自定义的排序,例如按绝对值大小排序:
>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]
我们再看一个字符串排序的例子,实现忽略大小写的排序:
>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower) ['about', 'bob', 'Credit', 'Zoo']
要进行反向排序,不必改动key函数,可以传入第三个参数reverse=True。
一组tuple排序
L=[('Bob',75),('Adam',92),('Bart',66),('Lisa',88)] def by_name(t): return t[0] L2=sorted(L,key=by_name) print(L2)
返回函数
函数作为返回值
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。
我们来实现一个可变参数的求和:
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)”。
闭包
注意到返回的函数在其定义内部引用了局部变量args
,所以,当一个函数返回了一个函数后,其内部的局部变量还被新函数引用,所以,闭包用起来简单,实现起来可不容易。
另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了f()
才执行。
返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
如果一定要引用循环变量怎,方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:
def count(): def f(j): return lambda : j*j fs=[] for i in range(1,4): fs.append(f(i)) return fs f1,f2,f2=count() print(type(f1)) print(f1(),f2(),f3()) result: <class,'function'> 1 4 9
匿名函数
当我们在传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便。
Python对匿名函数的支持有限,只有一些简单的情况下可以使用匿名函数。匿名函数有个限制,就是只能有一个表达式,不用写return
,返回值就是该表达式的结果。
通过对比可以看出,匿名函数lambda x: x * x
关键字lambda
表示匿名函数,冒号前面的x
表示函数参数。
匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数,同样,也可以把匿名函数作为返回值返回:
>>> f = lambda x: x * x >>> f <function <lambda> at 0x101c6ef28> >>> f(5) 25
装饰器
函数对象有一个__name__
属性,可以拿到函数的名字:
>>> def now(): ... print('2015-3-25')
>>> f = now
>>> now.__name__
'now'
>>> f.__name__
'now'
现在,假设我们要增强now()
函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()
函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。
和两层嵌套的decorator相比,3层嵌套的效果是这样的:
>>> now = log(now) #两层
>>> now = log('execute')(now) #三层
不需要编写wrapper.__name__ = func.__name__
这样的代码,Python内置的functools.wraps
就是干这个事的,只需记住在定义wrapper()
的前面加上@functools.wraps(func)
即可。
写一个decorator,能在函数调用的前后打印出'begin call'
和'end call'
的日志。
import functools def log(arg=None): if isinstance(arg, str): print(arg) return log @functools.wraps(arg) def decorator(): print('Start') arg() print('End') return decorator @log def now(): print('Processing') if __name__ == '__main__': now()
偏函数
functools.partial
就是帮助我们创建一个偏函数的,简单总结functools.partial
的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数。
注意到上面的新的int2
函数,仅仅是把base
参数重新设定默认值为2
,但也可以在函数调用时传入其他值:
import functools #偏函数就是设置了默认参数之后的函数 int2 = functools.partial(int, base = 2) print(int2('10010')) #如果不指明将那个参数设置成默认值的话,那么python将按自动匹配 max10 = functools.partial(max, 10,9,8) print(max10(1,2,3,4,5))
创建偏函数时,实际上可以接收函数对象、*args
和**kw
这3个参数。
整理自:http://www.liaoxuefeng.com