Skip to content

01-函数基础

Python 版本要求:Python 3.11+ 贯穿项目:functions_demo/ 代码位置app/core/functions.py测试验证cd functions_demo && uv run pytest -k TestFunctionsBasics -v

本章代码基于 Python 3.11+ 编写

函数是代码复用的基础,掌握函数定义和调用是编程的核心技能。


概念铺垫

为什么需要函数?一个真实的重复代码场景

问题场景: 你在开发一个学生成绩管理系统,需要计算多个班级的平均分。

不使用函数的麻烦:

python
# 班级 A 的成绩
scores_a = [85, 92, 78, 96, 88]
total_a = 0
for score in scores_a:
    total_a += score
average_a = total_a / len(scores_a)
print(f"班级 A 平均分:{average_a}")

# 班级 B 的成绩
scores_b = [90, 87, 93, 85, 91]
total_b = 0
for score in scores_b:
    total_b += score
average_b = total_b / len(scores_b)
print(f"班级 B 平均分:{average_b}")

# 班级 C 的成绩...又要复制粘贴一遍?

问题:

  • 同样的代码重复写多次
  • 如果计算逻辑要修改,需要改多处
  • 代码冗长,难以维护

使用函数的解决方案:

python
def calculate_average(scores: list[int]) -> float:
    """计算平均分"""
    return sum(scores) / len(scores)

# 简洁调用
scores_a = [85, 92, 78, 96, 88]
scores_b = [90, 87, 93, 85, 91]

print(f"班级 A 平均分:{calculate_average(scores_a)}")
print(f"班级 B 平均分:{calculate_average(scores_b)}")

这就是函数的价值:把重复的代码打包起来,一次定义,多次使用


函数解决了什么问题?

函数的本质是:给一段代码起个名字,需要时直接调用

就像你把常用的菜谱写下来,以后照着做就行,不用每次重新想。

函数的优势:

  1. 避免重复:同样的代码写一次,调用多次
  2. 分而治之:把大问题拆成小函数,各个击破
  3. 易于修改:改一处函数,所有调用处自动生效
  4. 便于测试:可以单独验证每个函数是否正确

L1 理解层:会用

函数的最简用法

函数的核心操作:定义、调用、返回值。

python
# 定义函数
def greet(name: str) -> str:
    """打招呼"""
    return f"你好,{name}!"

# 调用函数
message = greet("张三")
print(message)  # 你好,张三!

这就是函数的基本用法。接下来我们详细学习函数的所有功能。


第一部分:函数的定义和调用

什么是函数

函数(Function) 是一段有名字的、可以反复调用的代码块。

┌─────────────────────────────────────────────────────────────┐
│                    函数 = 打包好的代码                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   输入(参数)                                              │
│       │                                                     │
│       ▼                                                     │
│  ┌─────────────┐                                            │
│  │   函数内部   │  ← 处理逻辑                                │
│  └─────────────┘                                            │
│       │                                                     │
│       ▼                                                     │
│   输出(返回值)                                            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

基本语法

python
def 函数名(参数列表) -> 返回类型:
    """文档字符串(可选)"""
    函数体
    return 返回值

示例代码

python
# 定义函数
def greet() -> None:
    """打招呼的函数"""
    print("Hello, World!")

# 调用函数(可以多次调用)
greet()  # 输出:Hello, World!
greet()  # 输出:Hello, World!
greet()  # 输出:Hello, World!

语法要点:

元素说明
def关键字,宣告"我要定义一个函数"
函数名用动词命名,如 calculate_areaget_user
()括号,即使没有参数也不能省略
->返回类型注解(Python 3.5+,可选)
:冒号,不能省略
缩进函数体必须缩进(通常 4 个空格)

第二部分:带参数的函数

单个参数

python
def greet(name: str) -> None:
    """向指定的人打招呼"""
    print(f"Hello, {name}!")

greet("Alice")   # Hello, Alice!
greet("Bob")     # Hello, Bob!

多个参数

python
def introduce(name: str, age: int, city: str) -> None:
    """自我介绍"""
    print(f"我叫 {name},今年 {age} 岁,来自 {city}")

introduce("张三", 25, "北京")
# 我叫 张三,今年 25 岁,来自 北京

第三部分:返回值

return 语句

python
def add(a: int, b: int) -> int:
    """计算两个数的和"""
    result = a + b
    return result

answer = add(3, 5)
print(answer)  # 8

# 返回值可以直接参与计算
print(add(10, 20) * 2)  # 60

没有 return 的函数

python
def say_hello() -> None:
    print("Hello!")

result = say_hello()  # 打印 Hello!
print(result)         # None

重要: Python 中所有函数都有返回值。没有写 return 时,自动返回 None

多个返回值

python
def get_info() -> tuple[str, int, str]:
    """返回多个值(实际返回元组)"""
    name = "Alice"
    age = 25
    city = "北京"
    return name, age, city

# 接收多个返回值
n, a, c = get_info()
print(n, a, c)  # Alice 25 北京

# 或者作为元组接收
info = get_info()
print(info)     # ('Alice', 25, '北京')

第四部分:函数文档字符串

docstring 的作用

python
def calculate_area(width: float, height: float) -> float:
    """
    计算矩形面积
    
    Args:
        width: 宽度(正数)
        height: 高度(正数)
    
    Returns:
        面积值
    
    Example:
        >>> calculate_area(5, 3)
        15
    """
    return width * height

# 查看文档
print(calculate_area.__doc__)

# 使用 help() 查看完整文档
help(calculate_area)

渐进复杂:从简单到复杂的函数

层级 1:无参数无返回值

python
def say_hi() -> None:
    print("Hi!")

say_hi()

层级 2:有参数无返回值

python
def greet(name: str) -> None:
    print(f"Hello, {name}!")

greet("张三")

层级 3:有参数有返回值

python
def add(a: int, b: int) -> int:
    return a + b

result = add(1, 2)

层级 4:多参数多返回值

python
def divide_and_remainder(a: int, b: int) -> tuple[int, int]:
    """返回商和余数"""
    return a // b, a % b

quotient, remainder = divide_and_remainder(10, 3)
print(f"商:{quotient},余数:{remainder}")

层级 5:带类型注解的完整函数

python
def calculate_grade(
    scores: list[float],
    weights: list[float] | None = None
) -> dict[str, float | str]:
    """
    计算成绩等级
    
    Args:
        scores: 分数列表
        weights: 权重列表(可选)
    
    Returns:
        包含平均分和等级的字典
    """
    if weights:
        total = sum(s * w for s, w in zip(scores, weights))
        avg = total / sum(weights)
    else:
        avg = sum(scores) / len(scores)
    
    if avg >= 90:
        grade = "A"
    elif avg >= 80:
        grade = "B"
    elif avg >= 70:
        grade = "C"
    elif avg >= 60:
        grade = "D"
    else:
        grade = "F"
    
    return {"average": avg, "grade": grade}

# 使用
result = calculate_grade([85, 90, 78, 92])
print(result)  # {'average': 86.25, 'grade': 'B'}

关键代码说明:

代码含义为什么这样写
weights: list[float] | None = None权重参数默认为 None用 None 而非 [] 作默认值,避免可变默认参数陷阱
sum(s * w for s, w in zip(scores, weights))将每个分数与对应权重相乘后求和zip 把两个列表配对,生成器表达式避免创建中间列表
total / sum(weights)除以权重总和而非元素数量权重可能不等于 1,必须按实际权重比例归一化
-> dict[str, float | str]返回值为含 float 或 str 的字典函数同时返回数值(平均分)和字符串(等级),用联合类型精确标注

实际应用:项目代码示例

本节展示 functions_demo/app/core/functions.py 中的实际代码,涵盖基础函数定义、高阶函数和递归。

基础函数:成绩等级与平均分

python
def letter_grade(score: float) -> str:
    """按百分制返回等级"""
    if score >= 90:
        return "A"
    elif score >= 80:
        return "B"
    elif score >= 70:
        return "C"
    elif score >= 60:
        return "D"
    return "F"


def calculate_average(scores: list[float]) -> float:
    """计算平均分"""
    if not scores:
        return 0.0
    return sum(scores) / len(scores)

这两个函数展示了单一职责原则——letter_grade 只负责等级转换,calculate_average 只负责平均分计算。

高阶函数:函数作为参数和返回值

python
from collections.abc import Callable

def apply_to_all(
    scores: list[float], transform: Callable[[float], float]
) -> list[float]:
    """对所有分数应用变换(函数作为参数)"""
    return [transform(s) for s in scores]


def make_threshold_checker(threshold: float) -> Callable[[float], bool]:
    """返回一个"是否达线"检查函数(函数作为返回值)"""
    def checker(score: float) -> bool:
        return score >= threshold
    return checker

使用示例:

python
# 函数作为参数:对每个成绩应用平方根变换
import math
scores = [81, 64, 100]
transformed = apply_to_all(scores, math.sqrt)  # [9.0, 8.0, 10.0]

# 函数作为返回值:创建个性化的检查器
is_excellent = make_threshold_checker(90)
is_excellent(95)  # True
is_excellent(85)  # False

递归:归并排序

项目代码中包含一个递归实现的 merge_sort,展示了函数调用自身的经典场景:

python
def merge_sort(items: list[float]) -> list[float]:
    """归并排序(递归实现)
    
    拆分 → 递归排序左右子列 → 合并
    """
    if len(items) <= 1:
        return list(items)
    mid = len(items) // 2
    left = merge_sort(items[:mid])
    right = merge_sort(items[mid:])
    return _merge(left, right)

递归的核心是基线条件len(items) <= 1)和递归步骤(拆分后调用自身)。


L2 实践层:最佳实践

推荐做法

做法原因示例
单一职责每个函数只做一件事,便于测试和复用calculate_average() 只计算平均分,不打印结果
函数名用动词语义清晰,一看就知道做什么get_user()calculate_total()validate_input()
参数数量 <= 5参数太多难以理解和调用超过 5 个参数考虑用数据类或字典封装
添加类型注解IDE 提示更准确,静态检查可发现错误def add(a: int, b: int) -> int:
编写 docstring自动生成文档,help() 可查看说明"""计算平均分"""
用 None 作默认值避免可变默认参数陷阱def func(items: list | None = None):
早返回减少嵌套层级,代码更扁平不符合条件的尽早 return

反模式:不要这样做

python
# ❌ 函数做了太多事
def process_user(name, scores):
    # 计算、判断、打印、保存...全在一起
    avg = sum(scores) / len(scores)
    if avg >= 60:
        print(f"{name} 通过")
        save_to_db(name, avg)
    else:
        print(f"{name} 未通过")

# 问题:
# 1. 一个函数做了计算、判断、打印、保存四个动作
# 2. 无法单独测试"计算"部分
# 3. 如果不需要打印,必须修改函数
# 4. 如果保存逻辑变化,也要修改这个函数
python
# ✅ 正确做法:拆分为单一职责的函数
def calculate_average(scores: list[float]) -> float:
    """只负责计算平均分"""
    return sum(scores) / len(scores)

def is_passed(average: float) -> bool:
    """只负责判断是否通过"""
    return average >= 60

def format_result(name: str, passed: bool) -> str:
    """只负责格式化结果字符串"""
    status = "通过" if passed else "未通过"
    return f"{name} {status}"

def process_user(name: str, scores: list[float]) -> None:
    """协调各函数完成流程"""
    avg = calculate_average(scores)
    passed = is_passed(avg)
    print(format_result(name, passed))
    # 保存逻辑可以单独处理
python
# ❌ 函数名不清楚含义
def f(x, y):
    return x * y + 10

def data(a):
    return a * 0.8

# 问题:看到调用 f(3, 5) 时,完全不知道做什么
python
# ✅ 正确做法:用描述性的动词命名
def calculate_adjusted_price(base_price: float, quantity: int) -> float:
    """计算调整后的价格"""
    return base_price * quantity + 10

def apply_discount(price: float) -> float:
    """应用折扣"""
    return price * 0.8
python
# ❌ 参数太多,顺序容易搞混
def create_user(name, age, city, email, phone, address, company, department):
    pass

create_user("张三", 25, "北京", "test@email.com", "13800000000",
            "朝阳区", "ABC公司", "研发部")  # 顺序错了很难发现
python
# ✅ 正确做法:用数据类封装参数
from dataclasses import dataclass

@dataclass
class UserInfo:
    name: str
    age: int
    city: str
    email: str
    phone: str = ""
    address: str = ""
    company: str = ""
    department: str = ""

def create_user(user: UserInfo) -> dict:
    """创建用户"""
    return {"name": user.name, "email": user.email}

# 调用时更清晰
user_data = UserInfo(name="张三", age=25, city="北京", email="test@email.com")
create_user(user_data)
python
# ❌ 深层嵌套
def process_data(data):
    if data:
        if data["type"] == "A":
            if data["value"] > 0:
                if data["status"] == "active":
                    return data["value"] * 2
    return 0

# 问题:4层嵌套,难以阅读和测试
python
# ✅ 正确做法:早返回(Guard Clause)
def process_data(data: dict | None) -> int:
    """处理数据"""
    if not data:
        return 0

    if data["type"] != "A":
        return 0

    if data["value"] <= 0:
        return 0

    if data["status"] != "active":
        return 0

    return data["value"] * 2

适用场景

场景是否推荐原因
计算逻辑✅ 推荐纯函数,无副作用,易于测试
数据转换✅ 推荐输入→输出,语义清晰
验证检查✅ 推荐返回布尔值,可复用
打印输出❓ 看情况如果只是为了打印,考虑直接写脚本
全局状态修改❌ 不推荐副作用难以追踪,优先用类封装
超过 20 行❌ 不推荐拆分成多个小函数

L3 专家层:底层原理

Python 如何实现函数调用

Python 函数调用涉及调用栈帧对象(Frame Object)的底层机制。

函数调用栈结构:
┌─────────────────────────────────────────────────────────────┐
│                      Python 调用栈                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   栈顶(当前执行)                                           │
│   ┌─────────────────────────────────────────────────────┐   │
│   │  Frame: inner()                                      │   │
│   │  局部变量: z = "inner"                               │   │
│   │  代码指针: 执行到 print(z)                           │   │
│   └─────────────────────────────────────────────────────┘   │
│                         ↓ 压栈                              │
│   ┌─────────────────────────────────────────────────────┐   │
│   │  Frame: outer()                                      │   │
│   │  局部变量: y = "outer"                               │   │
│   │  代码指针: inner() 调用处                            │   │
│   └─────────────────────────────────────────────────────┘   │
│                         ↓ 压栈                              │
│   ┌─────────────────────────────────────────────────────┐   │
│   │  Frame: main 模块                                    │   │
│   │  局部变量: x = "global"                              │   │
│   │  代码指针: outer() 调用处                            │   │
│   └─────────────────────────────────────────────────────┘   │
│                                                             │
│   调用流程:                                                 │
│   1. 函数调用 → 创建新帧对象压入栈顶                        │
│   2. 帧对象存储:局部变量、代码位置、返回地址                │
│   3. 函数返回 → 弹出帧对象,恢复上一帧执行                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘
python
# 查看帧对象
import sys

def show_frame() -> None:
    """演示帧对象"""
    frame = sys._getframe()  # 获取当前帧

    print(f"函数名: {frame.f_code.co_name}")
    print(f"局部变量: {frame.f_locals}")
    print(f"行号: {frame.f_lineno}")
    print(f"调用者: {frame.f_back.f_code.co_name if frame.f_back else 'None'}")

def caller() -> None:
    x = 10
    show_frame()

caller()
# 函数名: show_frame
# 局部变量: {'frame': <frame object at ...>}
# 行号: 8
# 调用者: caller

帧对象的关键属性

属性含义用途
f_code代码对象包含函数的字节码、常量、参数信息
f_locals局部变量字典函数内的所有局部变量
f_globals全局变量字典模块级别的变量
f_lineno当前执行行号调试时定位错误位置
f_back上一帧(调用者)追溯调用链

性能考量

操作时间复杂度说明
函数调用O(1)帧对象创建是固定开销
参数传递O(n)n 为参数数量,打包成元组
返回值传递O(1)直接返回对象引用
局部变量查找O(1)使用数组索引,不是字典查找

性能测试:函数调用开销

python
import timeit

# 直接计算
inline_time = timeit.timeit("1 + 1", number=1000000)
# 约 0.03 秒

# 函数调用计算
def add(a: int, b: int) -> int:
    return a + b

func_time = timeit.timeit("add(1, 1)", globals=globals(), number=1000000)
# 约 0.08 秒

# 结论:函数调用有额外开销(约 2-3 倍)
# 但代码组织性更重要,除非极端性能场景

优化建议:

python
# ❌ 不必要的函数调用(循环内反复调用)
def is_valid(x: int) -> bool:
    return x > 0

result = []
for x in range(1000000):
    if is_valid(x):  # 每次循环都调用函数
        result.append(x)

# ✅ 简单判断直接写(热点代码)
result = []
for x in range(1000000):
    if x > 0:  # 直接判断,无函数调用开销
        result.append(x)

# ✅ 或用列表推导式(编译优化)
result = [x for x in range(1000000) if x > 0]

设计动机

Python 为什么这样设计函数?

设计选择原因替代方案对比
def 关键字语义明确,一眼识别函数定义JavaScript 用 function,更长
无类型声明(可选注解)动态灵活,不强制类型Java/C++ 强制类型,编译检查但繁琐
return 可选没有 return 自动返回 NoneGo 强制返回值,更严格
多返回值(元组)简洁,无需定义结构体C 返回单一值或指针参数
docstring 一等公民文档与代码绑定,help() 直接看Java 文档分离(Javadoc)

函数对象本质:

python
# 函数是对象,可以像普通对象操作
def greet(name: str) -> str:
    return f"Hello, {name}"

# 函数对象属性
print(greet.__name__)     # 'greet'
print(greet.__doc__)      # None(没写 docstring)
print(greet.__code__)     # 代码对象

# 函数可以赋值、传递、存储
say_hello = greet         # 赋值给另一个变量
functions = [greet, len]  # 存在列表里
result = say_hello("Alice")  # 通过新名字调用

知识关联

函数知识关联图:
                    ┌───────────────┐
                    │   函数对象    │
                    │  __code__     │
                    └───────────────┘


┌─────────────┐     ┌───────────────┐     ┌───────────────┐
│  帧对象     │────→│   函数基础    │────→│   调用栈      │
│  sys._getframe│   │   def/return  │     │  执行流程     │
└─────────────┘     └───────────────┘     └───────────────┘


                    ┌───────────────┐
                    │   闭包        │
                    │   nonlocal    │
                    └───────────────┘


                    ┌───────────────┐
                    │   装饰器      │
                    │   函数包装    │
                    └───────────────┘

交互演示

运行项目 CLI 查看本章代码的实际执行效果:

bash
cd functions_demo && uv run python -m app   # 选 1

本章小结

┌─────────────────────────────────────────────────────────────┐
│                      函数基础 知识要点                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   L1 理解层:                                                │
│   ✓ 函数是打包好的代码块,可以反复调用                       │
│   ✓ 作用:避免重复、分而治之、易于修改、便于测试             │
│   ✓ 定义语法:def 函数名(参数) -> 返回类型:                 │
│   ✓ return 返回结果,没有 return 返回 None                  │
│                                                             │
│   L2 实践层:                                                │
│   ✓ 单一职责:一个函数只做一件事                            │
│   ✓ 函数名用动词:语义清晰                                  │
│   ✓ 参数数量 <= 5:太多则封装为数据类                        │
│   ✓ 早返回:减少嵌套层级                                    │
│                                                             │
│   L3 专家层:                                                │
│   ✓ 函数调用涉及帧对象和调用栈                              │
│   ✓ 帧对象存储局部变量、代码位置、返回地址                  │
│   ✓ 函数调用有固定开销,热点代码可内联                      │
│   ✓ 函数是一等对象,可赋值、传递、存储                      │
│                                                             │
└─────────────────────────────────────────────────────────────┘