Python函数之高阶函数&递归&匿名函数
一 高阶函数
变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另外一个函数作为参数,这种函数就称之为高阶函数。
def my_add(x,y,func):
return func(x) + func(y)
res = my_add(-11,5,abs)
print(res) # 16
只需要满足以下任意条件,即是高阶函数:
- 接收一个或多个函数作为输入
- return 返回另外一个函数
二 递归
2.1 什么是递归
我们知道,在函数内部,可以调用其他函数。如果一个函数在内部调用自己本身,这个函数就是递归函数。举个例子:采用递归的方式计算1~n的和:
def my_sum(n):
if n == 1:
return 1
return my_sum(n - 1) + n
print(my_sum(5)) # 15
实际的执行步骤我们可以拆解如下:
# my_sum(5) = my_sum(4) + 5 = my_sum(3) + 4 + 5 = my_sum(2) +3 + 4 + 5 = my_sum(1) + 2 + 3+ 4 + 5 = 1 + 2 + 3+ 4 + 5 = 15
递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。
2.2 最大层数限制
使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多(超过最大层数限制),会导致栈溢出。我们可以试试my_sum(1000):
在执行上面的示例时,会报错:
RecursionError: maximum recursion depth exceeded in comparison
如何修改最大层数限制?
import sys
a = sys.getrecursionlimit()
sys.setrecursionlimit(2000) # 修改最大层数限制
def func(n):
print(n)
func(n+1)
func(1)
虽然可以进行修改最大层数限制,但是因为不是尾递归,仍然要保存栈,内存大小一定,不可能无限递归,而且无限制地递归调用本身是毫无意义的。
2.3 尾递归
解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。
尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。
上面的my_sum(n)函数由于return my_sum(n - 1) + n引入了加法表达式,所以就不是尾递归了。要改成尾递归方式,需要多一点代码,主要是要把每一步的和传入到递归函数中:
def my_sum(n):
return my_sum_iter(n, 0)
def my_sum_iter(num, sum):
if num == 0:
return sum
return my_sum_iter(num - 1, num + sum)
my_sum(5)对应的my_sum_iter(5, 0)的调用如下:
# my_sum_iter(5, 0) # my_sum_iter(4, 5) # my_sum_iter(3, 9) # my_sum_iter(2, 12) # my_sum_iter(1, 14) # my_sum_iter(0, 15) # 15
尾递归调用时,如果做了优化,栈不会增长,因此,无论多少次调用也不会导致栈溢出。但大多数编程语言没有针对尾递归做优化,Python解释器也没有做优化,所以,即使把上面的my_sum(n)函数改成尾递归方式,也会导致栈溢出。
2.4 递归调用的两个阶段:递推&回溯
递归应该分为两个明确的阶段:回溯与递推
回溯:从外向里一层一层递归调用下去, 回溯阶段必须要有一个明确地结束条件,每进入下一次递归时,问题的规模都应该有所减少(否则,单纯地重复调用自身是毫无意义的)
递推:从里向外一层一层结束递归
2.5 实例应用
2.6 总结
综上,我们在使用递归时,需要注意以下几点:
1. 必须有一个明确的结束条件
2. 每次进入更深一层递归时,问题规模相比上次递归都应有所减少
3. Python标准的解释器没有针对尾递归做优化,任何递归函数都存在栈溢出的问题
三 匿名函数
3.1 概述
有些时候,当我们在传入函数时,不需要显式地定义函数,这时我们可以定义匿名函数。下面我们以map()函数为例展示匿名函数的使用,计算给定数字列表各元素的平方,组成新的列表:
L = [1, 2, 3, 4, 5, 6, 7, 8, 9] new_L = list(map(lambda x:x*x,L)) print(new_L) # [1, 4, 9, 16, 25, 36, 49, 64, 81]
通过对比可以看出,匿名函数lambda x:x*x实际上就是:
def func(x):
return x * x
图解匿名函数:
上面的例子效果等价于:
L = [1, 2, 3, 4, 5, 6, 7, 8, 9]
def func(x):
return x*x
new_L = []
for i in L:
new_L.append(func(i))
print(new_L) # [1, 4, 9, 16, 25, 36, 49, 64, 81]
当然,上面的例子我们用列表生成式更简单,这里只是为了说明匿名函数的使用。
用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,我们可以把匿名函数赋值给一个变量,再利用变量来调用该函数:
func = lambda x:x*x print(func) # <function <lambda> at 0x000001AC31A2F048> print(func(10)) # 100
但这样就失去了匿名函数的意义了
同时,我们可以将匿名函数作为返回值返回,例如:
def func():
return lambda x:x*x
print(func) # <function func at 0x000002ADB84AF048>
print(func()) # <function func.<locals>.<lambda> at 0x000002ADB84AF1E0>
print(func()(10)) # 100
匿名函数与有名函数的区别:
有名函数:循环使用,保存了名字,通过名字就可以重复引用函数功能
匿名函数:一次性使用,随时随时定义
3.2 实例应用
scores = {
'zhangsan':87,
'lisi':92,
'wangwu':78,
'xiaoming':99
}
max_score_name = max(scores,key = lambda k:scores[k])
print(max_score_name)
min_score_name = min(scores,key = lambda k:scores[k])
print(min_score_name)


浙公网安备 33010602011771号