目录
1.函数执行过程
2.链式调用
3.嵌套调用
4.函数递归
4.1 递归的概念
4.2 递归的优点
4.3 递归的缺点
5. 参数默认值
6. 关键字参数
7. 小结
1.函数执行过程
- 调用函数才会执行函数体代码. 不调用则不会执行.
- 函数体执行结束(或者遇到 return 语句), 则回到函数调用位置, 继续往下执行.
def test(): print("执行函数内部代码") print("执行函数内部代码") print("执行函数内部代码") print("1111") test() print("2222") test() print("3333")
这个过程还可以使用 PyCharm 自带的调试器来观察:
- 点击行号右侧的空白, 可以在代码中插入 断点
- 右键, Debug, 可以按照调试模式执行代码. 每次执行到断点, 程序都会暂停下来.
- 使用 Step Into (F7) 功能可以逐行执行代码.
2.链式调用
编程演示:# 判定是否是奇数 def isOdd(num): if num % 2 == 0: return False else: return True result = isOdd(10) print(result)
实际上也可以简化写作 print(isOdd(10))
把一个函数的返回值 , 作为另一个函数的参数 , 这种操作称为 链式调用 .def isOdd(num): if num % 2 == 0: return False else: return True def add(x,y): return x+y print(isOdd(add(6,1)))
这是一种比较常见的写法.
3.嵌套调用
函数内部还可以调用其他的函数 , 这个动作称为 " 嵌套调用 " .def test(): print("执行函数内部代码") print("执行函数内部代码") print("执行函数内部代码")
test 函数内部调用了 print 函数 , 这里就属于嵌套调用 .一个函数里面可以嵌套调用任意多个函数 .函数嵌套的过程是非常灵活的def a(): print("函数 a") def b(): print("函数 b") a() def c(): print("函数 c") b() def d(): print("函数 d") c() d()
如果把代码稍微调整, 打印结果则可能发生很大变化.def a(): print("函数 a") def b(): a() print("函数 b") def c(): b() print("函数 c") def d(): c() print("函数 d") d()
注意体会上述代码的执行顺序. 可以通过画图的方式来理解.函数之间的调用关系, 在 Python 中会使用一个特定的数据结构来表示, 称为 函数调用栈 . 每次函数调用, 都会在调用栈里新增一个元素, 称为 栈帧.
- 可以通过 PyCharm 调试器看到函数调用栈和栈帧.
- 在调试状态下, PyCharm 左下角一般就会显示出函数调用栈.
每个函数的局部变量 , 都包含在自己的栈帧中def a(): num1 = 10 print("函数 a") def b(): num2 = 20 a() print("函数 b") def c(): num3 = 30 b() print("函数 c") def d(): num4 = 40 c() print("函数 d") d()
- 选择不同的栈帧, 就可以看到各自栈帧中的局部变量.
- 调用函数,则生成对应的栈帧;
- 函数结束,则对应的栈帧消亡(里面的局部变量也就消失了)
思考 : 上述代码 , a, b, c, d 函数中的局部变量名各不相同 . 如果变量名是相同的 , 比如都是 nums , 那么这四个函数中的 nums 是属于同一个变量 , 还是不同变量呢 ?def a(): nums = 10 print("函数 a") def b(): nums = 20 a() print("函数 b") def c(): nums = 30 b() print("函数 c") def d(): nums = 40 c() print("函数 d") d()
- 每个变量虽然同名,但其是不同变量。属于不同的函数作用域。
- 每个变量也是保存在各自的栈帧中的(每个栈帧也是保存在内存上),变量本质就是一块内存空间。
4.函数递归
4.1 递归的概念
递归是嵌套调用 中的一种特殊情况 , 即一个函数嵌套调用自己 .代码示例 : 递归计算 5!#写一个函数,求n的阶乘(n是正整数) def factor(n): if n == 1: return 1 return n * factor(n - 1) result = factor(5) print(result)
上述代码中, 就属于典型的递归操作 . 在 factor 函数内部 , 又调用了 factor 自身 .def factor(n): result = 1 for i in range(1,n+1): result *= i return result print(factor(5))
注意 : 递归代码务必要保证
- 存在递归结束条件. 比如 if n == 1 就是结束条件. 当 n 为 1 的时候, 递归就结束了.
- 每次递归的时候, 要保证函数的实参是逐渐逼近结束条件的.
如果上述条件不能满足 , 就会出现 " 无限递归 " . 这是一种典型的代码错误:def factor(n): return n * factor(n - 1) result = factor(5) print(result)
- 如前面所描述, 函数调用时会在函数调用栈中记录每一层函数调用的信息.
- 但是函数调用栈的空间不是无限大的. 如果调用层数太多, 就会超出栈的最大范围, 导致出现问题.
4.2 递归的优点
- 递归类似于 "数学归纳法" , 明确初始条件, 和递推公式, 就可以解决一系列的问题.
- 递归代码往往代码量非常少.
4.3 递归的缺点
- 递归代码往往难以理解, 很容易超出掌控范围
- 递归代码容易出现栈溢出的情况
- 递归代码往往可以转换成等价的循环代码. 并且通常来说循环版本的代码执行效率要略高于递归版本.
实际开发的时候 , 使用递归要慎重 !
5. 参数默认值
Python 中的函数 , 可以给形参指定默认值 .带有默认值的参数 , 可以在调用的时候不传参 .代码示例 : 计算两个数字的和def add(x, y, debug=False): if debug: print(f'调试信息: x={x}, y={y}') return x + y print(add(10, 20)) print(add(10, 20, True))
此处 debug=False 即为参数默认值 . 当我们不指定第三个参数的时候 , 默认 debug 的取值即为 False.带有默认值的参数需要放到没有默认值的参数的后面def add(x, debug=False, y): if debug: print(f'调试信息: x={x}, y={y}') return x + y print(add(10, 20))
多个带有默认参数的形参,这些 都得放在后面!
6. 关键字参数
在调用函数的时候 , 需要给函数指定实参 . 一般默认情况下是按照形参的顺序 , 来依次传递实参的 .但是我们也可以通过 关键字参数, 来调整这里的传参顺序, 显式指定当前实参传递给哪个形参.
def test(x, y): print(f'x = {x}') print(f'y = {y}') test(x=10, y=20) test(y=100, x=200)
形如上述 test(x=10, y=20) 这样的操作 , 即为 关键字参数 .
- 位置参数和关键字参数还能混着使用,只不过混着用的时候要求位置参数在前,关键字参数在后。
- 关键字参数,一般搭配默认参数来使用的。
- 一个函数,可以提供很多的参数,来实现对这个函数的内部功能做出一些调整设定。为了降低调用者的使用成本,就可以把大部分参数设定为默认值。当调用者需要调整其中的一部分参数的时候,就可以搭配关键字参数来进行操作。
7. 小结
函数是编程语言中的一个核心语法机制 . Python 中的函数和大部分编程语言中的函数功能都是基本类似的.我们当下最关键要理解的主要就是三个点 :
- 函数的定义
- 函数的调用
- 函数的参数传递
我们在后续的编程中 , 会广泛的使用到函数 . 在练习的过程中再反复加深对于函数的理解 .