函数式编程其中的一个特点就是允许把函数本身作为参数传递给另一个函数,也允许返回一个函数,Python
支持函数式编程的这一特点。由于Python
允许使用变量,因此Python
不是纯函数式编程语言。
先了解函数作为变量是什么意思,以abs
函数为例,执行print(abs(-10))
的输出结果是10
,这里输出的是abs(-10)
的返回值;如果执行print(abs)
输出结果则是<built-in function abs>
,这里输出的则是abs
函数。其实abs
这个函数名也是变量,同样可以进行传递
f = abs
print(f(-10))
输出结果同样是10
,和直接调用其实是一样的,如果这里直接print(f)
的输出结果同样也是<built-in function abs>
,这说明f
函数已经指向了abs
函数,这就是函数的传递。这样子,函数就可以作为参数传递
def addAbs(x,y,f):
return f(x) + f(y)
sum = addAbs(-10,2,abs)
print(sum)
#输出结果为:12
函数可以作为变量传递,如果函数名在代码中使用了,并且赋了其他的值,这时候去调用这个变量将使用的是后面赋予的值,而不是原本的函数。实际上一般也不允许这么写
map
map
函数接收两个参数,一个函数,一个Iterable
,map
将对传入的Iterable
中的元素执行传入的函数,最后返回新的Iterable
。
def f(x):
return x + 10
r = map(f,[1,2,3,4,5,6,7,8,9])
print(list(r))
#输出结果为11~19
将函数f
传入map
迭代生成list
,map
作为高阶函数,将函数的规则运算抽象,不仅仅可以计算x+10
还可以计算更加复杂的函数。
reduce
reduce
函数同样也是接收函数和一个Iterable
,reduce
函数的作用就是将传入函数的结果与Iterable
的下一个元素累积计算,效果上感觉有点类似递归。
from functools import reduce
def add(x, y):
return x + y
print(reduce(add, [1, 2, 3, 4, 5]))
#输出结果为15
可以看出来是1~5的累加,这么看reduce
函数并没有什么特别的地方,但是给出了一个map
与reduce
结合使用的例子,将str
转换成int
,很简单,但是很好地使用了map
函数和reduce
函数
from functools import reduce
DIGTIS = {'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(c):
return DIGTIS[c]
return reduce(fn, map(char2num, s))
或者使用lambda
表达式
from functools import reduce
DIGTIS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
def char2num(c):
return DIGTIS[c]
def str2int(s):
return reduce(lambda x, y: x * 10 + y, map(char2num, s))
有时候觉得大神的想法真多,一个简单地东西,还能思考多种写法。反思一下,其实学习的过程中很多东西看懂了,却没有比较深入的使用,需要适当的做一些扩展的思考来练习知识的使用,不然知识始终只是知识而已。
对于map
和reduce
两个方法,Google很久以前写过一篇论文MapReduce: Simplified Data Processing on Large Clusters,看了能更好地理解map/reduce
。
filter
filter
函数,用于过滤,与map
相似接收一个函数和一个Iterable
。filter
函数将传入的函数以此作用于传入的序列,函数返回True
或False
表示该元素是否保留。
def check_empty(s):
return s and s.strip()
l = list(filter(check_empty, ['X', '', 'Y', None, 'Z', ' ']))
print(l)
#输出结果:['X', 'Y', 'Z']
sorted
Python内置的sorted
函数,可以对list
进行排序,不仅仅是int
,字符串类型也可以进行排序,字符串排序则是按照ASCII
的大小进行比较的。
l = [86, 16, -64, 0, -3]
print(sorted(l))
#输出结果:[-64, -3, 0, 16, 86]
这是对int
正常大小的排序,sorted
还可以接收一个key
函数来实现自定义排序,比如:按绝对值排序
l = [86, 16, -64, 0, -3]
print(sorted(l, key=abs))
#输出结果:[0, -3, 16, -64, 86]
接下来看看字符串的排序
l = ['MrTrying', 'C.C.', 'nothing', 'bruce', 'jack wharton']
print(sorted(l))
#输出结果:['C.C.', 'MrTrying', 'bruce', 'jack wharton', 'nothing']
在ASCII
中,大写字符的的ASCII
值是小于小写字符的,所以默认情况下,大写字母开头的会排在前面;当然通过key
可以忽略大小写的比较
l = ['MrTrying', 'C.C.', 'nothing', 'bruce', 'jack wharton']
print(sorted(l, key=str.lower))
#输出结果:['bruce', 'C.C.', 'jack wharton', 'MrTrying', 'nothing']
sorted
函数还有第三个参数reverse
,reverse=True
可以将排序反序
l = ['MrTrying', 'C.C.', 'nothing', 'bruce', 'jack wharton']
print(sorted(l, key=str.lower, reverse=True))
#输出结果:['nothing', 'MrTrying', 'jack wharton', 'C.C.', 'bruce']
基本可以看出sorted
函数可以将排序算法抽象的功能,提升了排序的扩展性,同时代码还是相当简洁的。sorted
函数对list
进行排序,就可以对dict
的key
或者value
进行排序。
返回(一个)函数
Pyhton
支持函数式编程,函数可以作为参数传递,也可以作为返回值使用。
这是一个求和函数
def calc_sum(*args):
x = 0
for n in args:
x = x + n
return x
可以将这个函数作为返回值return
出去,被赋值的函数使用方式是一样的。
def lazy_sum():
def calc_sum(*args):
x = 0
for n in args:
x = x + n
return x
return calc_sum
f = lazy_sum()
print(f)
#输出结果:<function lazy_sum.<locals>.calc_sum at 0x1047a02f0>
print(f(1,2,3,4,5))
#输出结果:15
每次调用lazy_sum()
都返回一个新函数,即使传参是一样的
闭包
闭包,简单一点理解就是:能够读取其他函数内部变量的函数。
def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs
f1, f2, f3 = count()
上面的count
函数返回一个函数的list
,函数是循环生成的,其中f()
使用了count()
中的局部变量i
,i
的引用并保存在返回的函数,这种形式称之为闭包。
可能觉得返回值应该是1
、4
、9
,但是分别调用f1()
、f2()
、f3()
输出结果全部都是9
。原因在于引用了i
,i
在循环中递增,随着f1()
、f2()
、f3()
三个函数的生成i
变成了3
,导致最后输出都是9
。所以,使用返回闭包时,最好不要引用可变变量。
使用
java
会知道,java
不支持函数式编程,所以传递函数的替代方式是传递一个实现的接口,而在实例化这个接口的代码块中,如果接口的方法想要使用自身作用于意外的变量,这个变量就必须使用final
修饰。
上面的代码稍作修改,使用g()
返回f()
,f()
所使用的n
在调用g()
时传入不会在发生改变,进而f()
所保存的n
不会发生改变,再调用f1()
、f2()
、f3()
就可以正常输出1
、4
、9
def count():
def g(n):
def f():
return n * n
return f
fs = []
for i in range(1, 4):
fs.append(g(i))
return fs
f1, f2, f3 = count()
lambda表达式
函数传递时,可以使用匿名函数,简化代码
l = list(map(lambda x: x * x, [1, 2, 3, 4, 5]))
print(l)
#输出结果:[1, 4, 9, 16, 25]
lambda
后跟的就是函数的参数,:
后是函数返回值得表达式。
上面代码就可以看到,使用lambda x: x * x
代替了
def f(x):
return x * x
同样,lambda
表达式也可以作为函数返回
def f(x):
return lambda: x * x
Decorator(装饰器)
Decorator
是高阶函数,可以在不修改原有还是的基础上,动态的增加函数功能。
先定义一个打印日志的Decorator
,可以看到log
函数传入一个函数,返回在内部定义的wapper
函数。wapper
函数调用log
传入的函数func
,并在调用func
函数前打印了函数名
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args,**kw):
print('call %s():' % func.__name__)
return func(*args,**kw)
return wrapper
log
中返回的wrapper
函数的参数(*args,**kw)
可以接受任意参数的调用。
Decorator
可以借助pyhton
的@
语法放在定义函数的代码处。
@log
def test():
print('MrtTying')
test()
#输出结果:
#call test():
#MrtTying
这里看代码的写法,decorator
相当于执行了test = log(test)
,只是在原有的test
函数本身做了一个代理。如果需要自定义日志部分的分本呢?可以给之前的log
函数在套一层,log
函数传入自定义文本,decorator传入func
参数,具体如下:
import functools
def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
@log('call')
def test():
print('MrtTying')
而函数的实际调用相当于test = log('call')(test)
到这里已经有两种decorator
写法了,而wrapper
函数都有一个@functools.wraps(func)
的decorator
。这里分析一波,log
最终返回的是wrapper
函数,虽然wrapper
返回了传入函数的返回值,当外部调用test
函数时,实际上使用的是wrapper
函数,只是test函数重新指向了wrapper
函数。
这里就会出现一个问题,函数是一个对象,拥有__name__
等属性,而wrapper
函数的属性与传入的func
并不相同,所以我们需要将原有函数func
的属性传递给wrapper
函数,否则有些依赖函数签名的代码会出错。
Pyhton
的内置函数functools.wraps
可以帮助我们完成函数属性的复制。用法如上两段代码
偏函数
偏函數是对可以传入默认值的函数的一种扩展,可以重新创建一个函数设定这个传入函数的默认值,同样也是可以重新设置的。
def int2(x):
return int(x, base=2)
print(int2('01000000'))
#输出结果:64
这是我们自己的实现方式,重新定义了int2
函数来转化2进制数。python
中functools.partial
可以帮助我们创建一个偏函数
import functools
int2 = functools.partial(int, base=2)
print(int2('01000000'))
#输出结果:64
上述代码中的int2
函数,同样可以添加base
参数,例如:int2('01000000',base=10)
得到的结果将是'01000000'
的10进制结果,也就是1000000
。
创建偏函数时,实际可以接收函数对象、
*args
和***kw
,int
中只是固定了关键字参数是base
偏函数也就通过固定原函数的部分参数来简化函数调用。