Python不是纯函数式编程语言
高阶函数
变量与函数名
可以用一个变量指向一个函数名,而代替这个函数的操作。也可以说函数本身可以赋值给变量
1 | -10) abs( |
其实函数名本身也是一个变量,是指向函数的变量。下面的代码可以验证这个结论
1 | -10) abs( |
abs()
是系统提供的求绝对值得函数,我自定义了一个函数aa()
,让输入的数+100然后打印出来,然后把aa
函数名赋值给abs
,然后系统的求绝对值的函数就变成了打印+100操作的函数了。
要恢复abs()
的函数功能,要重启Python交互环境 exit()
-> python
函数名可以赋值给变量,函数名本身也是个指向函数的变量。函数可以接受变量为参数,那么函数也可以作为参数传给另一个函数。
这句绕口令的意思是:函数可以作为另一个函数的参数,这种函数称为高阶函数
。
自定义函数addnum(x,y,z)
包含了三个参数,其中参数z
是传入的是一个函数名,在addnum
函数内z,y
变为函数z
的参数,执行完函数z
操作,把结果相加打印出来。
1 | def addnum(x,y,z): |
map()
map()
函数传入两个参数,第一个参数是一个函数,第二个参数是一个可迭代对象Iterable
,函数将作用于序列的每一个元素,把结果作为生成器Iterator
返回。
1 | def st(s): #自定义函数st |
reduce()
reduce()
函数也接受两个参数,第一个是有两个参数的函数,第二个参数是可迭代对象Iterable
,两个元素作为参数作用于函数,结果和第三个元素作为参数作用于函数,依次运行下去,最后return结果
1 | reduce(fun, [x, y, z, m, n]) = fun(fun(fun(fun(x, y) z) m) n) |
filter()
filter()
函数用于筛选过滤序列。操作方式类似map()
,只是传入的函数返回值是bool
类型,如果是True
则保留该元素,如果是False
则清除该元素,最后剩下的list作为Iterator
返回
自定义函数,传入整数,偶数返回True
,奇数返回False
1 | def test(a): |
1 | 1,2,3,4,5,6,7])) list(filter(test, [ |
sorted()
sorted()
是排序算法的高阶函数,可以对list进行排序
1 | 10,2,3,4,-23,33]) sorted([ |
也可以对字符串的list进行排序,是根据ASCII码排序的
1 | 'a', 'B', 'Za', 'Zb']) sorted([ |
还可以接收一个参数key
函数,来实现自定义排序,比如按照绝对值大小排序
1 | 10,2,3,4,-23], key = abs) sorted([ |
这种实现原理是:key指向的函数作用于list里的每一个元素得到结果,把结果进行排序得到一个新list,命名为:newList,然后将原list按照这个newList里每个元素的对应关系返回相应的元素,实现排序
上文说到,默认的字符串排序是按照ASCII码排序的,大写字母的ASSCII码值要小于小写字母的ASSCII码值,所以'A'
要排在'a'
前面。如果要实现按照字母表顺序呢?其实只要忽略每个字符串的大小写就OK
1 | 'a', 'B', 'Za', 'Zb'], key = str.lower) sorted([ |
要实现反向排序,只需要加入第三个参数reverse = True
1 | 'a', 'B', 'Za', 'Zb'], key = str.lower, reverse = True) sorted([ |
返回函数
函数作为返回值
函数既然可以作为参数传递到函数中,自然也可以作为返回值被函数return
出来。
定义一个求平方的函数
1 | def test(a): |
将该函数作为返回值放到另一个函数testTest()
中
1 | def testTest(b): |
调用
1 | 10) w = testTest( |
在调用testTest()
的时候,每次调用都是返回一个全新的函数。即便是传入相同的参数
1 | 10) w = testTest( |
闭包
外部函数testTest()
包含了内部函数test()
,内部函数test()
引用了外部函数变量。调用外部函数,在返回函数的时候,包括函数的相关参数都一起返回了。这种程序结构被称为闭包
。
还有一个注意点,调用外部函数,返回的函数并没有立即执行,只有在调用的时候才会执行。返回的函数不要引用任何可能会变化的变量。
下面一个例子说明返回函数没有立即执行,和返回函数引用会变化的变量引发的问题。
1 | def test1(): |
定义函数test1()
,函数内包含了一个循环,循环的x
是一个变化的变量,内部函数fs()
引用了外部这个可变化的变量x
。每次循环生成一个fs()
函数,存放到array
数组中,最后返回array
数组,里面放了四个fs
函数。
1 | f1, f2, f3, f4 = test1() |
最后的结果不是1
,2
,3
,4
,而全部都是4
该函数的运行原理是:调用test1()
,执行函数内的for
循环,每一次循环都生成一个fs()
内部函数,每个内部函数都互不干扰,但是每个函数都引用的外部函数的x
,最后for
循环执行完毕,返回了存放函数的数组。这时里面的函数没有任何一个执行了,但是此时的x
早已变为4
了。拿到四个函数f1
f2
f3
f4
运行,所以全部返回4
。
所以说谨记一点:返回函数不要引用任何可变化的变量
匿名函数
匿名函数就是没有写函数名的函数。
1 | list(map(lambda x: x + x, [10,22,4,21])) |
这里面lambda x: x + x
就是匿名函数,lambda
是表示匿名函数的关键字,:
前是参数,后表达式。匿名函数只能有一个表达式,不用写return
,返回值就是该表达式的结果。
匿名函数也是函数,所以也可以赋值给变量,作为参数传入函数或被函数返回。
但是,Python对匿名函数支持有限,只有一些简单情况下可以用匿名参数
装饰器
不更改函数的情况下,动态增加函数功能的方式,称为装饰器(Decoratot)
函数的__name__
属性可以获得函数名
1 | test() |
装饰器是一个返回函数的高阶函数。
自定义函数,打印hello world:
1 | def test(): |
如何在不改动该函数的前提下,打印出调用该函数的函数名?这就使用到了装饰器
1 | def inputTest(func): |
上面代码就是一个打印日志的装饰器,inputTest
函数,传入了一个函数func
,返回了函数inputFunc
。函数内自定义的函数inputFunc
实现了打印传入函数func
的函数名,并执行传入函数。
如何将这个装饰器作用于test
函数?使用Python的@
语法。
1 |
|
将@inputTest
放到test()
函数定义上面,相当于执行了test = inputTest(test)
执行结果:
1 | test() |
装饰器本身如果需要加入参数,就需要编写一个返回装饰器的高阶函数。例如打印log需要自定义文本+函数名
1 | def log(text): |
执行结果:
1 | test() |
偏函数
Python的functools
模块提供了很多功能,其中一种就是偏函数(Partial function)。
之前说的函数的默认参数,指定某一函数的默认值,降低函数调用难度。偏函数也可以做到
函数int()
可以吧字符串转换为整数,默认按照十进制转换。该函数还有一个额外参数base
,可以指定按照某进制转换。注:int('2323232', base = 8)
是指字符串是八进制数据字符串,转为十进制整数
1 | '2323232') # 十进制转换十进制 int( |
需要大量转换二进制字符串时,每次传入int(x, base = 2)
太麻烦,自定义函数int2()
,转换二进制字符串
1 | def int2(x, base = 2): |
上述是基本做法,可以创建偏函数实现上述需求,不需要自定义函数。
1 | import functools |
functools.partial
是将函数的某个参数给定一个默认值,返回新的函数。int2 = functools.partial(int, base = 2)
就是指定int()
函数中base
参数的默认值是2
并返回新的函数。
创建偏函数时,实际上可以接收函数对象、*args
和 **kw
三个参数。
1 | 10) max2 = functools.partial(max, |
当函数参数太多时,需要简化,可以使用functools.partial
创建一个偏函数。可以固定原函数的部分参数,调用起来更简单。