python学习笔记——函数相关(不包括高阶函数和函数对功能的抽象)
python学习笔记——函数相关
定义函数和递归函数
函数的概念与定义
- 用于描述一个独立的功能模块
- 可自定义定义,也可重复调用
- 是代码复用和逻辑抽象的核心手段
- 函数定义后,函数名可绑定到新的名字(函数是一等对象)
def add_fun(x):
return x + 3
lmy = add_fun
print(lmy(10)) # 输出:13
# 覆盖绑定(新名字变为普通变量),绑定关系可以修改覆盖
lmy = 1
# lmy(10) → 报错:int不可调用
函数的调用
- 调用方式与 C++ 类似,通过「函数名 (参数)」执行函数体
- 调用时完成实参到形参的绑定,执行完毕返回结果
参数传递的方式
- 默认按位置逐一匹配实参和形参
def qwq(num, denom):
return num / denom
print(qwq(1, 3)) # 输出:0.3333333333333333(num=1,denom=3)
- 按照参数名进行传递(关键字参数),参数顺序可任意
def qwq(num, denom):
return num / denom
print(qwq(denom=3, num=1)) # 输出:0.3333333333333333
- 允许定义函数时指定参数默认值,调用时可省略该参数(若不省略会发生覆盖)
def lmy(x=3):
return x
print(lmy()) # 输出:3(使用默认值)
print(lmy(5)) # 输出:5(覆盖默认值)
- 可变长位置参数(*args):接收任意个数位置参数,打包为元组
def my_print(first, *args):
print(first)
print(args)
my_print(1, 2, 3) # 输出:1 (2, 3)
- 可变长关键字参数(**kwargs):接收任意个关键字参数,打包为字典
def my_print(**kwargs):
for k, v in kwargs.items():
print(f"{k} = {v}")
my_print(a=1, b=2) # 输出:a = 1 b = 2
函数返回值
- 支持返回单个值,也可返回多个值(自动打包为元组)
def calc(x, y):
return x+y, x-y # 多个返回值
sum_val, sub_val = calc(5, 3)
print(sum_val, sub_val) # 输出:8 2
函数嵌套调用
一个函数内部可调用另一个函数,实现逻辑分层
def square(x):
return x * x
def sum_square(a, b):
return square(a) + square(b)
print(sum_square(3, 4)) # 输出:25
一些函数特殊的技巧
文档字符串(DocString)
用于对函数功能、参数、返回值等进行说明,提升代码可读性
定义在函数体第一行,用单 / 双三引号包裹,支持多行编写
可通过 doc 属性或 help() 函数查看文档字符串内容
def calc(x, y):
"""
计算两个数的和与差
:param x: 第一个数字(int/float)
:param y: 第二个数字(int/float)
:return: 元组,第一个元素是和,第二个元素是差
"""
return x+y, x-y
# 查看文档字符串
print(calc.__doc__) # 输出函数的文档说明
help(calc) # 更友好的格式展示文档信息
类型注解(课程不要求内容)
函数的执行环境
• 解释器如何理解对函数的执行?
• 解释器如何处理函数内部和外部的变量绑定?
• 解释器如何处理不同函数的调用过程?
• 解释器如何处理同一个函数的多个调用过程?
Python 函数执行环境:Frame(栈帧)模型详解
在 Python 中,函数的执行过程,本质上是通过「Frame(帧 / 栈帧)」模型来管理的。它解决了变量、函数等名字的绑定与作用域问题,也是理解 Python 函数调用、作用域和闭包的核心基础。
什么是 Frame?
- Frame 是 Python 实现「函数计算模型」的核心,本质上是一个名字绑定的容器,用来记录代码执行时的变量、函数等名字与实际对象的对应关系。
- 每个 Frame 都包含独立的绑定关系(变量名、函数名 → 实际对象 / 值)
- Frame 之间形成层级关系,构成「环境链」,Python 会按特定规则在链中查找名字的绑定。
- 程序执行的第一个 Frame 是 Global Frame(全局帧),也就是我们常说的「全局作用域」,它是所有 Frame 的 “根节点”。
全局帧:程序的起点
当 Python 程序开始运行时,会首先创建一个全局帧。所有在函数外部定义的变量、函数,都会被绑定到这个全局帧中。
示例:函数定义阶段的全局帧绑定
def add_by_3(x):
return x + 3
x = 5
当执行 def add_by_3(x): 时,Python 并不会立刻执行函数体,
而是把 add_by_3 这个名字,绑定到定义好的函数对象上,
记录在全局帧中。
执行 x = 5 时,全局帧中会新增一条绑定:x → 5。
这一步的关键是:函数定义本身不执行函数体,
只是完成了「函数名与函数对象」的绑定。
函数调用:局部帧的创建
当函数被调用时,Python 会为这次调用创建一个全新的 Local Frame(局部帧 / 局部作用域)。
- 局部帧里存了什么?
- 形参与实参的绑定关系:比如调用 add_by_3(100) 时,形参 x 会被绑定为实参 100,记录在局部帧中。
- 函数执行过程中产生的新绑定:比如函数内部定义的变量,都会保存在当前的局部帧里。
- 函数的返回值:函数执行结束后,返回值会被记录在局部帧中,再传递给调用者。
- 名字查找规则:从局部到全局
函数执行时,查找变量的顺序遵循「就近原则」:
Local Frame
Global Frame
依然找不到,就会抛出 NameError 异常
函数执行结束:返回值与局部帧销毁
当函数执行到 return 语句时,会完成以下流程:
- 计算返回值,并将其记录在当前的 Local Frame 中。
- 将返回值传递给调用者(比如 y = add_by_3(x) 中,返回值会被赋值给 y)。
- 函数执行完毕,当前的局部帧会被销毁,帧内的所有绑定关系也随之消失。
多次调用同一个函数:独立的局部帧
同一个函数被多次调用时,每次调用都会创建一个全新的、独立的局部帧,帧与帧之间互不干扰。
生成器generator相关知识(了解,不强制要求*生成器函数)



函数副作用
核心概念:纯函数 vs 非纯函数
✅ 纯函数(Pure-Function)
定义:执行过程不影响函数外部状态,只根据输入参数完成计算并返回结果。
特点:相同输入永远得到相同输出(无状态依赖)
没有副作用(不修改外部变量、不读写文件 / 控制台)
def add(a, b):
return a + b # 只依赖输入,不修改任何外部状态
⚠️ 非纯函数(Non-Pure Function)
定义:执行过程中会改变函数外部的状态,这种影响就叫「函数副作用」。
程序状态包括:非局部变量、生成器状态、控制台输出、文件读写、网络请求等。
- 副作用的影响:
优点:可以通过修改外部状态传递信息
缺点:让调试、单元测试、并发编程变得更困难
参数传递与副作用的关系
Python 的参数传递本质是「对象引用传递」,副作用的关键区别在于参数对象是否可变:
不可变(Immutable)参数:天然避免副作用
- 不可变对象:int、float、str、tuple 等
原理:函数内对形参赋值时,会让形参绑定到新的对象,不会修改原对象本身。
def printID(x):
print(x, ":", id(x))
def work_print(x):
x += 5 # 等价于 x = x + 5,形参x绑定到新的int对象
printID(x)
x = 3
printID(x) # 输出 3 : 4332774608
work_print(x) # 输出 8 : 4332774768(新对象)
printID(x) # 输出 3 : 4332774608(原对象未变)
结论:不可变参数的修改不会影响外部实参,因此不会产生副作用。
可变(Mutable)参数:潜在的副作用风险
- 可变对象:list、dict、set 等
原理:函数内修改形参指向的对象时,会直接改变原对象的状态,产生副作用。
def change_print(x):
x[0] = 5 # 直接修改x指向的list对象的元素
printID(x)
y = [0, 1, 2, 3]
printID(y) # 输出 [0, 1, 2, 3] : 140350183780832
change_print(y) # 输出 [5, 1, 2, 3] : 140350183780832(同一对象)
printID(y) # 输出 [5, 1, 2, 3] : 140350183780832(原对象被修改)
结论:可变参数的修改会直接影响外部实参,这是 Python 中最常见的副作用来源。
特殊注意:不可变对象中也可能包含可变元素,比如 tuple 中包含 list:
t = ([1, 2], 3)
def modify_tuple(x):
x[0].append(4) # 修改tuple内的list元素
modify_tuple(t)
print(t) # 输出 ([1, 2, 4], 3),原tuple状态被改变,产生副作用
函数的功能检查
核心概念:算法正确性的两个维度
算法正确性的完整定义是:
对任意合法输入,经过有限步执行后,给出正确结果。
它被拆成了两个关键部分:
- 部分正确性(Partial Correctness)
- 定义:只要算法最终能停止运行,那么它返回的结果一定是正确的。
特点:不保证算法一定会停止,只保证 “停止就一定对”。
- 完全正确性(Total Correctness)
- 定义:算法不仅结果正确,还必须对所有合法输入最终停止运行。
关键难点:停机问题(Halting Problem)—— 图灵已经证明,不存在通用算法能判断任意程序是否会停止运行。
算法正确 vs 程序正确(区分两个容易混淆的概念)
| 维度 | 算法正确 | 程序正确 |
|---|---|---|
| 关注点 | 输入→输出的逻辑关系 | 程序运行过程中的状态变化 |
| 核心要求 | 对合法输入给出正确结果 | 程序状态的改变必须和算法设计完全 |
| 例子 | 上面的奇完全数算法,逻辑上是对的 | 但如果用 C 语言实现时出现了数组越界、内存泄漏,程序就是不正确的 |
保障程序正确性的三种方法
1. 形式化验证(Formal Verification)
核心思路:用数学逻辑来证明程序的正确性,而不是靠运行测试。
常见技术:
- 模型检测(Model Checking):遍历程序的所有状态,验证是否满足预设的属性(比如 “不会出现死锁”)。
- 定理证明(Theorem Proving):用逻辑公式描述程序和需求,再用数学推理证明两者等价。
-
- 特点:严谨但成本高,常用于航空航天、芯片设计等对安全性要求极高的场景。
2. 程序分析(Program Analysis)
- 静态分析:不运行程序,直接分析代码,发现潜在错误(比如空指针、未初始化变量)。
- 动态分析:在程序运行时监控状态,检测运行时错误(比如内存泄漏、竞态条件)。
- 特点:成本比形式化验证低,能发现很多常见 bug,但无法保证 100% 覆盖所有情况。
3. 软件测试(Software Testing)
核心思路:设计测试用例,运行程序并对比结果是否符合预期。
- 常见类型:单元测试、集成测试、系统测试、回归测试等。
-
- 特点:最常用的方法,但它只能证明 “存在错误”,不能证明 “没有错误”(即无法做到完全验证)。
函数的测试
基础测试:assert 语句
- 核心原理
assert <expression>, <error_message> 是 Python 内置的断言语句:
当 expression 为 True 时,程序继续执行,无任何输出。
当 expression 为 False 时,程序抛出 AssertionError 异常,
并输出 error_message。
def compute(x, y):
""" compute and return the value of x + y and x - y """
return x + y, x - y
def compute_test():
""" test function for compute """
assert compute(3, 5) == (8, -2), 'wrong computing for 3, 5'
assert compute(5, 3) == (8, 2), 'wrong computing for 5, 3'
当函数逻辑正确时,调用 compute_test() 无任何输出。
若函数逻辑出错(比如返回了 x+y, x+y),则会抛出异常:
plaintext
AssertionError: wrong computing for 3, 5
错误信息直接告诉你哪一组测试用例失败,快速定位问题。
进阶测试:doctest 文档字符串测试
核心原理
doctest 模块允许你直接在函数的文档字符串中,嵌入交互式的调用示例和预期结果,然后自动运行这些示例进行测试。
def compute_with_doctest(x, y):
"""
compute and return the value of x + y and x - y
>>> compute_with_doctest(3, 5)
(8, -2)
>>> compute_with_doctest(5, 3)
(8, 2)
"""
return x + y, x + y # 这里故意写错,模拟bug
from doctest import testmod
testmod()
运行 testmod() 后,doctest 会自动执行文档中的示例,并对比实际输出和预期结果:
plaintext
Failed example:
compute_with_doctest(3, 5)
Expected:
(8, -2)
Got:
(8, 8)
它清晰地列出了失败的用例、预期结果和实际结果,非常方便调试。

浙公网安备 33010602011771号