Skip to content

章节3 函数与模块化编程

授课时长:约4-5学时
适用对象:已掌握Python基础语法和数据类型的学员
教学方式:讲授 + 代码推导 + 模式分析


一、学习目标

目标分类具体目标
知识目标理解函数定义、参数传递机制、作用域规则、闭包与装饰器原理
技能目标熟练编写装饰器、生成器、高阶函数,掌握模块导入与包管理
素养目标建立模块化编程思维,写出可复用、可维护的代码

二、核心知识点

1️⃣ 函数定义参数传递返回值

1.1 函数定义基础

python
# 基本语法
def 函数名(参数列表):
    """文档字符串"""
    函数体
    return 返回值

# 示例
def greet(name):
    """向指定的人打招呼"""
    return f"你好,{name}!"

print(greet("小明"))       # 你好,小明!

1.2 参数传递机制

Python的参数传递方式称为 "传对象引用"(call by object reference)。

python
"""
核心规则:
- 可变对象(list/dict/set)在函数内修改会影响原对象
- 不可变对象(int/str/tuple)在函数内修改不会影响原对象
- 但若将可变对象重新赋值,则不会影响原对象
"""

def modify_list(lst):
    lst.append(4)           # ✅ 修改原列表
    print(f"函数内: {lst}")

def reassign_list(lst):
    lst = [5, 6, 7]         # ❌ 重新赋值,原列表不受影响
    print(f"函数内: {lst}")

my_list = [1, 2, 3]
modify_list(my_list)
print(f"函数外: {my_list}")  # [1, 2, 3, 4]

reassign_list(my_list)
print(f"函数外: {my_list}")  # [1, 2, 3, 4](不变)

1.3 参数类型详解

python
# ──────────────────────────────────────────
# 1. 位置参数
def power(base, exp):
    return base ** exp

print(power(2, 3))       # 8  位置对应

# ──────────────────────────────────────────
# 2. 默认参数
def register(name, age, city="北京"):
    return f"{name}({age}岁) - {city}"

print(register("Alice", 20))              # 使用默认city
print(register("Bob", 22, "上海"))         # 覆盖默认值

# ⚠️ 默认参数的陷阱(重要!)
def add_student(name, courses=[]):   # ❌ 可变对象做默认参数
    courses.append(name)
    return courses

print(add_student("Alice"))   # ['Alice']
print(add_student("Bob"))     # ['Alice', 'Bob'] ❌ 共享了同一个列表

# ✅ 正确做法
def add_student_correct(name, courses=None):
    if courses is None:
        courses = []
    courses.append(name)
    return courses

# ──────────────────────────────────────────
# 3. 关键字参数
def introduce(name, age, city):
    print(f"{name}, {age}岁, 来自{city}")

introduce(age=25, city="深圳", name="Charlie")  # 顺序可乱

# ──────────────────────────────────────────
# 4. 可变位置参数 *args
def sum_all(*args):
    """接收任意数量的位置参数"""
    print(f"参数元组: {args}")
    return sum(args)

print(sum_all(1, 2, 3, 4, 5))    # 15
print(sum_all(10, 20))            # 30

# ──────────────────────────────────────────
# 5. 可变关键字参数 **kwargs
def build_profile(**kwargs):
    """接收任意数量的关键字参数"""
    print(f"参数字典: {kwargs}")
    profile = []
    for key, value in kwargs.items():
        profile.append(f"{key}={value}")
    return ", ".join(profile)

print(build_profile(name="Alice", age=25, city="北京"))

# ──────────────────────────────────────────
# 6. 参数组合(顺序固定)
def func(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
    """
    参数顺序:
    1. 仅位置参数 (/) 前的参数只能按位置传递
    2. 普通参数
    3. 仅关键字参数 (*) 后的参数只能按关键字传递
    """
    print(f"仅位置: {pos1}, {pos2}")
    print(f"位置或关键字: {pos_or_kwd}")
    print(f"仅关键字: {kwd1}, {kwd2}")

func(1, 2, 3, kwd1="a", kwd2="b")       # ✅
# func(pos1=1, pos2=2, 3, kwd1="a", kwd2="b")  # ❌ pos1不能传关键字

1.4 返回值

python
# 单个返回值
def square(x):
    return x ** 2

# 多个返回值(本质是元组)
def stats(numbers):
    return min(numbers), max(numbers), sum(numbers) / len(numbers)

result = stats([1, 2, 3, 4, 5])
print(result)                 # (1, 5, 3.0)
mini, maxi, avg = stats([1, 2, 3, 4, 5])

# 提前返回
def divide(a, b):
    if b == 0:
        return None          # 提前返回
    return a / b

# 无返回值的函数返回 None
def say_hello(name):
    print(f"Hello, {name}")

result = say_hello("Alice")
print(result)                # None

2️⃣ 作用域命名空间与函数嵌套

2.1 LEGB 规则

Python 查找变量遵循 LEGB 规则(由内到外):

python
"""
L — Local(局部作用域)—— 当前函数内部
E — Enclosing(嵌套作用域)—— 外层函数
G — Global(全局作用域)—— 模块级别
B — Built-in(内置作用域)—— Python内置命名空间
"""

x = "global x"          # 全局作用域

def outer():
    x = "outer x"       # 嵌套作用域

    def inner():
        x = "inner x"   # 局部作用域
        print(f"inner: {x}")

    inner()
    print(f"outer: {x}")

outer()
print(f"global: {x}")

# 输出:
# inner: inner x
# outer: outer x
# global: global x

2.2 global 与 nonlocal

python
count = 0

def increment():
    global count        # 声明使用全局变量
    count += 1

increment()
print(count)            # 1

# nonlocal — 修改嵌套作用域变量
def outer():
    x = 10

    def inner():
        nonlocal x      # 声明使用外层函数的x
        x += 5
        print(f"inner: {x}")

    inner()
    print(f"outer: {x}")

outer()  # inner: 15, outer: 15

2.3 函数嵌套

python
def make_math_operation(operation):
    """根据参数返回不同的内部函数"""
    def add(a, b):
        return a + b

    def subtract(a, b):
        return a - b

    def multiply(a, b):
        return a * b

    if operation == "+":
        return add
    elif operation == "-":
        return subtract
    elif operation == "*":
        return multiply
    else:
        raise ValueError("不支持的运算")

# 使用
op = make_math_operation("+")
print(op(10, 5))        # 15

op = make_math_operation("*")
print(op(10, 5))        # 50

3️⃣ 闭包与装饰器原理及应用

3.1 闭包(Closure)

定义:内层函数引用了外层函数的变量(非全局),即使外层函数已经返回,内层函数仍然能记住这些变量。

python
def make_counter():
    """创建一个计数器闭包"""
    count = 0

    def counter():
        nonlocal count
        count += 1
        return count

    return counter

# 创建两个独立的计数器
c1 = make_counter()
c2 = make_counter()

print(c1())  # 1
print(c1())  # 2
print(c1())  # 3
print(c2())  # 1 (c2独立,从1开始)
print(c2())  # 2

# 检查闭包
print(c1.__closure__)       # (<cell at ...>,)
print(c1.__code__.co_freevars)  # ('count',)

闭包的应用场景

  • 数据隐藏(封装私有状态)
  • 延迟计算
  • 函数工厂
  • 装饰器的底层实现

3.2 装饰器(Decorator)

定义:装饰器是一个接受函数作为参数并返回新函数的可调用对象,用于在不修改原函数代码的情况下扩展其功能。

python
# ──────────────────────────────────────────
# 基础装饰器
def my_timer(func):
    """计算函数执行时间的装饰器"""
    import time

    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        elapsed = time.time() - start
        print(f"{func.__name__} 执行耗时: {elapsed:.4f}秒")
        return result

    return wrapper

# 使用装饰器
@my_timer
def slow_function():
    import time
    time.sleep(1)
    return "完成"

slow_function()

# ──────────────────────────────────────────
# 带参数的装饰器
def repeat(n=2):
    """让函数重复执行 n 次"""
    def decorator(func):
        def wrapper(*args, **kwargs):
            results = []
            for _ in range(n):
                result = func(*args, **kwargs)
                results.append(result)
            return results
        return wrapper
    return decorator

@repeat(n=3)
def say_hello(name):
    return f"Hello, {name}!"

print(say_hello("Alice"))
# ['Hello, Alice!', 'Hello, Alice!', 'Hello, Alice!']

# ──────────────────────────────────────────
# 保持元数据(重要!)
import functools

def log_call(func):
    @functools.wraps(func)   # ✅ 保持原函数的 __name__ 和 __doc__
    def wrapper(*args, **kwargs):
        print(f"调用: {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@log_call
def add(a, b):
    """两数相加"""
    return a + b

print(add.__name__)   # 'add'(没有@wraps会变成'wrapper')
print(add.__doc__)    # '两数相加'

3.3 装饰器实际应用

python
import functools
import time
from typing import Callable, Any

# ──────────────────────────────────────────
# 应用1:权限检查
def require_admin(func: Callable) -> Callable:
    @functools.wraps(func)
    def wrapper(user, *args, **kwargs):
        if not getattr(user, "is_admin", False):
            raise PermissionError("需要管理员权限")
        return func(user, *args, **kwargs)
    return wrapper

# ──────────────────────────────────────────
# 应用2:缓存(记忆化)
def memoize(func: Callable) -> Callable:
    """缓存函数计算结果"""
    cache = {}

    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        # 用参数作为键(简单版本)
        key = str(args) + str(sorted(kwargs.items()))
        if key not in cache:
            cache[key] = func(*args, **kwargs)
        return cache[key]
    return wrapper

@memoize
def fibonacci(n: int) -> int:
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

# 第一次计算缓慢,之后极快
print(fibonacci(35))

# ──────────────────────────────────────────
# 应用3:重试机制
def retry(max_attempts: int = 3, delay: float = 1.0):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            last_exception = None
            for attempt in range(1, max_attempts + 1):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    last_exception = e
                    print(f"第{attempt}次尝试失败: {e}")
                    if attempt < max_attempts:
                        time.sleep(delay)
            raise last_exception
        return wrapper
    return decorator

@retry(max_attempts=3, delay=0.5)
def unstable_network_request(url: str) -> str:
    """模拟不稳定的网络请求"""
    import random
    if random.random() < 0.7:  # 70%概率失败
        raise ConnectionError("网络连接失败")
    return f"成功获取: {url}"

4️⃣ 迭代器生成器与高阶函数

4.1 迭代器(Iterator)

python
"""
迭代器协议:
- __iter__() 返回迭代器对象自身
- __next__() 返回下一个元素,无元素时抛出 StopIteration
"""

# 手动使用迭代器
my_list = [1, 2, 3]
iterator = iter(my_list)      # 获取迭代器

print(next(iterator))       # 1
print(next(iterator))       # 2
print(next(iterator))       # 3
# print(next(iterator))     # StopIteration

# 自定义迭代器类
class CountDown:
    """倒计时迭代器"""
    def __init__(self, start):
        self.current = start

    def __iter__(self):
        return self

    def __next__(self):
        if self.current <= 0:
            raise StopIteration
        value = self.current
        self.current -= 1
        return value

for num in CountDown(5):
    print(num, end=" ")       # 5 4 3 2 1

4.2 生成器(Generator)

定义:使用 yield 关键字的函数,返回一个生成器对象,支持惰性求值(按需生成值,节省内存)。

python
# ──────────────────────────────────────────
# 基本生成器
def count_up_to(n):
    """从1数到n(惰性生成)"""
    count = 1
    while count <= n:
        yield count
        count += 1

# 使用
counter = count_up_to(5)
print(next(counter))       # 1
print(next(counter))       # 2

for num in count_up_to(5):
    print(num, end=" ")    # 1 2 3 4 5

# ──────────────────────────────────────────
# 生成器表达式(生成器推导式)
gen = (x ** 2 for x in range(1000000))
print(next(gen))           # 0 (不占用大内存)

# 对比列表推导式(内存差异巨大)
# list_comp = [x ** 2 for x in range(1000000)]   # 占用大量内存
# gen_expr = (x ** 2 for x in range(1000000))     # 几乎不占内存

# ──────────────────────────────────────────
# 生成器的实际应用:大文件逐行读取
def read_large_file(file_path):
    """逐行读取大文件,不一次性加载到内存"""
    with open(file_path, "r", encoding="utf-8") as f:
        for line in f:
            yield line.strip()

# 使用
# for line in read_large_file("huge_file.txt"):
#     process(line)

# ──────────────────────────────────────────
# yield from 语法(委托给子生成器)
def chain(*iterables):
    """串联多个可迭代对象"""
    for it in iterables:
        yield from it  # 委托给子生成器

combined = chain("ABC", [1, 2, 3])
print(list(combined))   # ['A', 'B', 'C', 1, 2, 3]

# ──────────────────────────────────────────
# 生成器的 send() 方法(双向通信)
def echo():
    while True:
        received = yield
        print(f"收到: {received}")

gen = echo()
next(gen)          # 启动生成器(执行到yield)
gen.send("Hello")  # 收到: Hello
gen.send(42)       # 收到: 42

4.3 高阶函数

python
# ──────────────────────────────────────────
# map() — 映射
nums = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x ** 2, nums))
print(squared)                  # [1, 4, 9, 16, 25]

# 多个可迭代对象
a = [1, 2, 3]
b = [10, 20, 30]
result = list(map(lambda x, y: x + y, a, b))
print(result)                   # [11, 22, 33]

# ──────────────────────────────────────────
# filter() — 过滤
nums = [1, 2, 3, 4, 5, 6, 7, 8]
evens = list(filter(lambda x: x % 2 == 0, nums))
print(evens)                    # [2, 4, 6, 8]

# ──────────────────────────────────────────
# reduce() — 归约
from functools import reduce

nums = [1, 2, 3, 4, 5]
total = reduce(lambda x, y: x + y, nums)  # ((((1+2)+3)+4)+5)
print(total)                    # 15

# 计算阶乘
factorial = reduce(lambda x, y: x * y, range(1, 6))
print(factorial)                # 120

# ──────────────────────────────────────────
# sorted() — 排序(key参数是高阶用法)
students = [
    {"name": "Alice", "score": 92},
    {"name": "Bob", "score": 85},
    {"name": "Charlie", "score": 95},
]

# 按分数排序
sorted_students = sorted(students, key=lambda s: s["score"], reverse=True)
print(sorted_students)

# ──────────────────────────────────────────
# 综合案例:数据管道
def process_data(numbers):
    """函数式数据处理管道"""
    return (
        numbers
        |> map(lambda x: x * 2)        # 但Python没有管道操作符
    )

# Python 方式(链式调用)
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
result = list(
    map(lambda x: x ** 2,
        filter(lambda x: x % 2 == 0,
               map(lambda x: x + 1, data)))
)
print(result)  # 先+1,再过滤偶数,再平方

4.4 lambda 表达式

python
# 语法:lambda 参数: 表达式

# 基础
square = lambda x: x ** 2
print(square(5))         # 25

# 多参数
add = lambda a, b: a + b
print(add(3, 5))         # 8

# 排序时指定key(最常见的用途)
pairs = [(1, 'one'), (3, 'three'), (2, 'two')]
pairs.sort(key=lambda x: x[0])   # 按第一个元素排序
print(pairs)

# ⚠️ 不要滥用lambda —— 复杂逻辑用 def
# ✅ lambda 适用于简单表达式
# ❌ lambda x: (lambda y: x + y)(x)  # 过度使用,难以阅读

5️⃣ 模块导入机制与包管理

5.1 模块导入

python
# ──────────────────────────────────────────
# 五种导入方式
import math                          # 导入整个模块
from math import sqrt                # 导入特定函数
from math import sin, cos, tan       # 导入多个
from math import *                   # ⚠️ 不推荐(命名空间污染)
import math as m                     # 别名

print(math.pi)          # 3.141592653589793
print(sqrt(16))         # 4.0
print(m.log(100, 10))   # 2.0

# ──────────────────────────────────────────
# __name__ 变量的使用
# my_module.py
def main():
    print("这是模块的主函数")

if __name__ == "__main__":
    # 只有直接运行此文件时才会执行
    main()

5.2 包结构

python
"""
包是一个包含 __init__.py 文件的目录

项目结构示例:
my_project/
├── main.py
├── requirements.txt
└── mypackage/
    ├── __init__.py        # 包的初始化文件
    ├── module1.py         # 模块1
    ├── module2.py         # 模块2
    └── subpackage/
        ├── __init__.py
        └── module3.py

# __init__.py 中可以定义:
"""

# mypackage/__init__.py
__version__ = "1.0.0"
__all__ = ["module1", "module2"]   # 控制 from package import * 的行为

# 使用
# from mypackage import module1
# from mypackage.subpackage import module3

5.3 模块搜索路径

python
import sys

# 查看模块搜索路径
for path in sys.path:
    print(path)

# 动态添加搜索路径
sys.path.append("/path/to/my/modules")

# 更好的方式:使用 PYTHONPATH 环境变量
# export PYTHONPATH=/path/to/my/modules:$PYTHONPATH

5.4 常用标准库模块

python
# ──────────────────────────────────────────
# os — 操作系统接口
import os
print(os.name)              # 'nt' (Windows) / 'posix' (Linux/macOS)
print(os.getcwd())          # 当前工作目录
# os.mkdir("new_folder")    # 创建目录
# os.listdir(".")           # 列出目录内容
# os.path.join("a", "b")    # 路径拼接

# ──────────────────────────────────────────
# sys — Python运行时
import sys
print(sys.version)           # Python版本
print(sys.argv)              # 命令行参数
print(sys.platform)          # 操作系统平台

# ──────────────────────────────────────────
# datetime — 日期时间
from datetime import datetime, timedelta
now = datetime.now()
print(now.strftime("%Y-%m-%d %H:%M:%S"))
tomorrow = now + timedelta(days=1)

# ──────────────────────────────────────────
# json — JSON处理
import json
data = {"name": "Alice", "scores": [95, 88, 92]}
json_str = json.dumps(data, ensure_ascii=False, indent=2)
print(json_str)

parsed = json.loads(json_str)
print(parsed["name"])

# ──────────────────────────────────────────
# random — 随机数
import random
print(random.random())           # 0.0 ~ 1.0
print(random.randint(1, 100))    # 1 ~ 100 随机整数
print(random.choice(["a", "b", "c"]))  # 随机选择
random.shuffle(["a", "b", "c"])  # 打乱顺序

5.5 第三方包管理

bash
# requirements.txt 格式
requests==2.28.1
numpy>=1.21.0
pandas
flask~=2.3.0

# 安装
pip install -r requirements.txt

# 生成
pip freeze > requirements.txt

三、代码实操演示

演示1:装饰器实现日志系统

python
import functools
import logging
from datetime import datetime

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger("App")


def log_function_call(level=logging.INFO):
    """记录函数调用的装饰器"""
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            args_repr = [repr(a) for a in args]
            kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]
            signature = ", ".join(args_repr + kwargs_repr)
            logger.log(level, f"调用 {func.__name__}({signature})")

            try:
                result = func(*args, **kwargs)
                logger.log(level, f"{func.__name__} 返回: {result!r}")
                return result
            except Exception as e:
                logger.error(f"{func.__name__} 抛出异常: {e}")
                raise
        return wrapper
    return decorator


@log_function_call()
def calculate_discount(price: float, rate: float = 0.8) -> float:
    """计算折扣价"""
    return price * rate

# 测试
print(calculate_discount(100, 0.75))

演示2:生成器实现无限斐波那契数列

python
def fibonacci_generator():
    """无限生成斐波那契数列"""
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b


# 使用
fib = fibonacci_generator()

# 取前10个
first_10 = [next(fib) for _ in range(10)]
print("前10个:", first_10)     # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

# 跳过10个,再取5个
for _ in range(10):
    next(fib)
next_5 = [next(fib) for _ in range(5)]
print("再5个:", next_5)

# 使用 itertools.islice 截取
import itertools
fib2 = fibonacci_generator()
selected = list(itertools.islice(fib2, 10, 20))
print("第11-20项:", selected)

四、常见错误与排错

错误类型示例原因与解决
UnboundLocalError函数内赋值变量后被当作局部变量需要在函数内声明 globalnonlocal
TypeError: missing argument调用函数时参数个数不匹配检查函数定义与调用时的参数数量
TypeError: 'list' object is not callablelist = [1,2,3]list()变量名覆盖了内置函数名
RecursionError递归调用超过最大深度检查递归终止条件,或增加 sys.setrecursionlimit
NameError: name 'xxx' is not defined模块未导入import xxx 后再使用
ModuleNotFoundError导入不存在的模块检查是否已安装 (pip install)

五、小结

知识图谱

函数与模块化编程
├── 函数定义
│   ├── 参数:位置/默认/关键字/*args/**kwargs
│   ├── 返回值:单值/多值(元组)/None
│   └── 传递机制:传对象引用
├── 作用域
│   ├── LEGB规则:Local → Enclosing → Global → Built-in
│   ├── global:修改全局变量
│   └── nonlocal:修改外层变量
├── 高阶特性
│   ├── 闭包:函数+环境变量
│   ├── 装饰器:@语法糖,AOP编程
│   ├── 生成器:yield惰性求值
│   └── 高阶函数:map/filter/reduce
└── 模块化
    ├── import机制
    ├── 包结构(__init__.py)
    └── pip包管理

重点回顾

  1. 装饰器是Python最强大的特性之一,理解闭包是理解装饰器的前提
  2. 生成器使用惰性求值,处理大数据集时比列表内存效率高成千上万倍
  3. 参数传递的核心是"传对象引用"——可变对象的内容可能被函数修改
  4. 避免使用可变对象作为默认参数,这是新手最常踩的坑之一
  5. 模块化编程让代码可复用、可维护、可测试

六、课后练习

基础题

  1. 函数练习:编写函数 is_prime(n) 判断素数,并利用它找出100以内的所有素数。
  2. 默认参数陷阱:解释为什么下面的代码输出不符合预期,并修复:def add_item(item, lst=[]): lst.append(item); return lst
  3. 模块导入:创建一个工具模块 string_utils.py,包含 reverse_strcount_vowels 两个函数,然后在另一个文件中导入使用。

进阶题

  1. 装饰器实现:编写一个 @timeout(max_seconds) 装饰器,当函数执行超过指定时间时抛出异常。
  2. 生成器应用:实现一个 read_chunks(file_path, chunk_size=1024) 生成器函数,每次返回指定大小的数据块,用于处理大文件的逐块读取。
  3. 高阶函数:使用 mapfilterreduce(或结合lambda)处理一个学生成绩列表,完成:过滤掉不及格(<60)的、将分数加5分、计算总分和平均分。

挑战题

  1. 缓存装饰器:实现一个带过期时间的缓存装饰器 @cache(ttl=60),缓存在ttl秒后失效。支持函数参数为任意可哈希类型。
  2. 管道模式:实现一个简单的函数管道,让多个函数可以串联执行。例如:pipe(数据, 函数1, 函数2, 函数3) 等价于 函数3(函数2(函数1(数据)))。提示:可以使用 reduce

参考资料

Python 学习资料