Python不是纯函数式编程语言
高阶函数
变量与函数名
可以用一个变量指向一个函数名,而代替这个函数的操作。也可以说函数本身可以赋值给变量
1 | abs(-10) |
其实函数名本身也是一个变量,是指向函数的变量。下面的代码可以验证这个结论
1 | abs(-10) |
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 | list(filter(test, [1,2,3,4,5,6,7])) |
sorted()
sorted()是排序算法的高阶函数,可以对list进行排序
1 | sorted([10,2,3,4,-23,33]) |
也可以对字符串的list进行排序,是根据ASCII码排序的
1 | sorted(['a', 'B', 'Za', 'Zb']) |
还可以接收一个参数key函数,来实现自定义排序,比如按照绝对值大小排序
1 | sorted([10,2,3,4,-23], key = abs) |
这种实现原理是:key指向的函数作用于list里的每一个元素得到结果,把结果进行排序得到一个新list,命名为:newList,然后将原list按照这个newList里每个元素的对应关系返回相应的元素,实现排序
上文说到,默认的字符串排序是按照ASCII码排序的,大写字母的ASSCII码值要小于小写字母的ASSCII码值,所以'A'要排在'a'前面。如果要实现按照字母表顺序呢?其实只要忽略每个字符串的大小写就OK
1 | sorted(['a', 'B', 'Za', 'Zb'], key = str.lower) |
要实现反向排序,只需要加入第三个参数reverse = True
1 | sorted(['a', 'B', 'Za', 'Zb'], key = str.lower, reverse = True) |
返回函数
函数作为返回值
函数既然可以作为参数传递到函数中,自然也可以作为返回值被函数return出来。
定义一个求平方的函数
1 | def test(a): |
将该函数作为返回值放到另一个函数testTest()中
1 | def testTest(b): |
调用
1 | w = testTest(10) |
在调用testTest()的时候,每次调用都是返回一个全新的函数。即便是传入相同的参数
1 | w = testTest(10) |
闭包
外部函数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 | int('2323232') # 十进制转换十进制 |
需要大量转换二进制字符串时,每次传入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 | max2 = functools.partial(max, 10) |
当函数参数太多时,需要简化,可以使用functools.partial创建一个偏函数。可以固定原函数的部分参数,调用起来更简单。