python学习笔记——函数相关(不包括高阶函数和函数对功能的抽象)

python学习笔记——函数相关

定义函数和递归函数

函数的概念与定义

  • 用于描述一个独立的功能模块
  • 可自定义定义,也可重复调用
  • 是代码复用和逻辑抽象的核心手段
  • 函数定义后,函数名可绑定到新的名字(函数是一等对象)
def add_fun(x):
    return x + 3
lmy = add_fun
print(lmy(10))  # 输出:13
# 覆盖绑定(新名字变为普通变量),绑定关系可以修改覆盖
lmy = 1
# lmy(10) → 报错:int不可调用

函数的调用

  • 调用方式与 C++ 类似,通过「函数名 (参数)」执行函数体
  • 调用时完成实参到形参的绑定,执行完毕返回结果

参数传递的方式

  1. 默认按位置逐一匹配实参和形参
def qwq(num, denom):
    return num / denom
print(qwq(1, 3))  # 输出:0.3333333333333333(num=1,denom=3)
  1. 按照参数名进行传递(关键字参数),参数顺序可任意
def qwq(num, denom):
    return num / denom
print(qwq(denom=3, num=1))  # 输出:0.3333333333333333
  1. 允许定义函数时指定参数默认值,调用时可省略该参数(若不省略会发生覆盖)
def lmy(x=3):
    return x
print(lmy())   # 输出:3(使用默认值)
print(lmy(5))  # 输出:5(覆盖默认值)
  1. 可变长位置参数(*args):接收任意个数位置参数,打包为元组
def my_print(first, *args):
    print(first)
    print(args)
my_print(1, 2, 3)  # 输出:1  (2, 3)
  1. 可变长关键字参数(**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

函数返回值

  1. 支持返回单个值,也可返回多个值(自动打包为元组)
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 会按特定规则在链中查找名字的绑定。
  • 程序执行的第一个 FrameGlobal 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(局部帧 / 局部作用域)。

  1. 局部帧里存了什么?
  • 形参与实参的绑定关系:比如调用 add_by_3(100) 时,形参 x 会被绑定为实参 100,记录在局部帧中。
  • 函数执行过程中产生的新绑定:比如函数内部定义的变量,都会保存在当前的局部帧里。
  • 函数的返回值:函数执行结束后,返回值会被记录在局部帧中,再传递给调用者。
  1. 名字查找规则:从局部到全局
    函数执行时,查找变量的顺序遵循「就近原则」:
    Local Frame
    Global Frame

依然找不到,就会抛出 NameError 异常

函数执行结束:返回值与局部帧销毁

当函数执行到 return 语句时,会完成以下流程:

  • 计算返回值,并将其记录在当前的 Local Frame 中。
  • 将返回值传递给调用者(比如 y = add_by_3(x) 中,返回值会被赋值给 y)。
  • 函数执行完毕,当前的局部帧会被销毁,帧内的所有绑定关系也随之消失。

多次调用同一个函数:独立的局部帧

同一个函数被多次调用时,每次调用都会创建一个全新的、独立的局部帧,帧与帧之间互不干扰。

生成器generator相关知识(了解,不强制要求*生成器函数)

c766e9da-761f-4d10-a5ea-d881dcef3d441e4801f5-b771-46d9-be6d-9e969506d53b
6058eac5-4b8c-457c-bf0c-2b190ccdced9

函数副作用

核心概念:纯函数 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状态被改变,产生副作用

函数的功能检查

核心概念:算法正确性的两个维度

算法正确性的完整定义是:

对任意合法输入,经过有限步执行后,给出正确结果。
它被拆成了两个关键部分

  1. 部分正确性(Partial Correctness)
  • 定义:只要算法最终能停止运行,那么它返回的结果一定是正确的。
    特点:不保证算法一定会停止,只保证 “停止就一定对”。
  1. 完全正确性(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)
它清晰地列出了失败的用例、预期结果和实际结果,非常方便调试。
posted @ 2026-03-23 10:49  RReally  阅读(11)  评论(0)    收藏  举报
//一下两个链接最好自己保存下来,再上传到自己的博客园的“文件”选项中