函数的执行过程、递归函数、匿名函数
函数的执行过程
举例:
1 | def foo1(b,b1=3): |
main called
foo1 called 100 101
foo3 called 200
foo2 called 200
main ending
以上代码执行流程:
- 全局帧生成foo1、foo2、foo3函数对象
- 主函数调用
- 主函数查找内建print压栈,将常量字符串压栈,调用函数,弹出栈顶
- 主函数中全局查找函数foo1压栈、将常量100、101压栈,调用函数foo1,创建栈帧。print函数压栈,字符串和变量吧b、b1压栈,调用函数,弹出栈顶,返回值
- 主函数查找foo2函数压栈、将常量200压栈,调用foo2、创建栈帧。foo3函数压栈,变量c引用压栈,调用foo3,创建栈帧。foo3完成print函数调用,返回值。
- 主函数中foo2调用结束后弹出栈顶,主函数继续执行print函数调用,弹出栈顶。
- 主函数结束调用, 返回None
递归函数
递归函数的含义
- 函数直接或间接调用自身就是递归
- 递归需要有边界条件、递归前进段、递归返回段
- 递归一定要有边界条件
- 当边界条件不满足时,递归前进
- 当边界条件满足时,递归返回
递归函数的性能
- 循环稍微复杂一些,但是只要不是死循环,可以多次迭代直至算出结果。
- fib函数代码极简易懂,但是只能获取最外层的函数调用,内部递归结果都是中间结果,而且给定一个n值都要进行近2n次递归,深度越深,效率越低。
- 递归还有深度限制,如果递归复杂,函数反复压栈,栈内存很快就会溢出
递归函数的优化
- 解决递归调用栈溢出的方法就是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以只要把循环看成是一种特殊的尾递归函数也是可以的
- 尾递归是指 在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。
- 这样编译器或者解释器就可以把尾递归优化,使递归本身无论调用多少次,都只占一个栈帧,不会出现栈溢出的情况
递归函数的总结
- 递归是一种很自然的表达,符合逻辑思维
- 递归相对运行效率低,每一次调用函数都要开辟栈帧
- 递归有深度限制,如果递归层次太深,函数反复压栈,栈内存很快就溢出了
- 如果是有限次数的递归,可以使用递归调用,或者使用循环代替,循环代码稍微复杂一些,但是只要不是死循环,可以多次迭代直至算出结果
- 绝大多数递归,都可以使用循环实现
- 即使递归代码很简洁,但是能不用则不用递归
递归函数的使用案例
1. 斐波那契数列
解法1:重点内容是 f(n) = f(n-1) + f(n-2)
1 | def f(n): |
1 1 2 3 5 8 13 21 34 55
解法2:利用函数体就是关系式,return就是返回结果进行计算
1 | def f(n,x=[1],y=[0]): |
1 1 2 3 5 8 13 21 34
55
2.求n的阶乘
1 | def f(n): |
120
求n的阶乘尾递归法
1 | def f(n,num=1): |
120
匿名函数
匿名函数就是没有名字的函数,但是,如果过函数没有名字,要如何定义? 要如何调用? 要如何使用?
- Python借助lambda表达式构建匿名函数
- 格式:
1 | # **lambda 参数列表:表达式 |
<function <lambda> at 0x000001EBD826E6A8>
16
foo(2,1): 9
foo(2,1): 9
匿名函数的定义
- 匿名函数使用 lambda关键字来定义
- 参数列表不需要小括号
- 冒号是用来分割参数列表和表达式
- 不需要使用return,表达式的值,就是匿名函数的返回值
- lambda表达式(匿名函数)只能写在一行上面 ,被称为单行函数
匿名函数的用途
在高阶函数传参时,使用lambda表达式,往往能简化代码
1 | print((lambda :0)()) |
0
8
11
35
15
<generator object <lambda>.<locals>.<genexpr> at 0x000001EBD82AB2A0>