Skip to content

第5章 函数与模块

学习目标

完成本章学习后,读者将能够:

  • 掌握Python函数参数的完整体系(位置参数、关键字参数、默认参数、可变参数、仅位置/仅关键字参数)
  • 理解LEGB作用域规则与闭包的实现原理
  • 运用递归与记忆化技术解决算法问题
  • 理解高阶函数与函数式编程范式
  • 掌握模块与包的组织规范,编写可复用的Python库

5.1 函数基础

5.1.1 函数定义与调用

python
def greet(name: str) -> str:
    """向指定用户打招呼"""
    return f"Hello, {name}!"

result = greet("Alice")
print(result)

# 多返回值(实际返回元组)
def get_stats(numbers: list[float]) -> tuple[float, float, float]:
    return min(numbers), max(numbers), sum(numbers)

minimum, maximum, total = get_stats([1, 2, 3, 4, 5])

5.1.2 文档字符串(Docstring)

python
def calculate_bmi(weight: float, height: float) -> float:
    """计算身体质量指数(BMI)

    使用国际标准公式:BMI = 体重(kg) / 身高(m)²

    Args:
        weight: 体重,单位千克(kg),必须为正数
        height: 身高,单位米(m),必须为正数

    Returns:
        BMI值,保留一位小数

    Raises:
        ValueError: 当体重或身高为非正数时

    Examples:
        >>> calculate_bmi(70, 1.75)
        22.9
    """
    if weight <= 0 or height <= 0:
        raise ValueError("体重和身高必须为正数")
    return round(weight / height ** 2, 1)

# 访问文档字符串
print(calculate_bmi.__doc__)
help(calculate_bmi)

5.1.3 类型注解

python
from typing import Optional, Union, Callable

# 基本类型注解
def greet(name: str, times: int = 1) -> str:
    return (f"Hello, {name}! ") * times

# 可选类型
def find_user(user_id: int) -> Optional[dict]:
    users = {1: {"name": "Alice"}}
    return users.get(user_id)

# 联合类型(Python 3.10+ 可用 X | Y 语法)
def process(value: Union[int, str]) -> str:
    return str(value)

# Python 3.10+
def process(value: int | str) -> str:
    return str(value)

# 回调函数类型
def apply(numbers: list[int], func: Callable[[int], int]) -> list[int]:
    return [func(n) for n in numbers]

5.2 参数体系

5.2.1 位置参数与关键字参数

python
def greet(name: str, message: str) -> str:
    return f"{name}, {message}"

# 位置参数:按顺序传递
greet("Alice", "你好!")

# 关键字参数:按名称传递,顺序无关
greet(message="你好!", name="Alice")

# 混合使用:位置参数在前,关键字参数在后
greet("Alice", message="你好!")

5.2.2 默认参数

python
def create_user(name: str, age: int = 18, city: str = "北京") -> dict:
    return {"name": name, "age": age, "city": city}

create_user("Alice")                    # 使用全部默认值
create_user("Bob", 25)                  # 覆盖age
create_user("Charlie", city="上海")      # 跳过age,覆盖city

关键陷阱:默认参数在函数定义时求值一次,而非每次调用时求值。可变默认参数会导致状态共享:

python
# 错误:可变默认参数
def add_item(item: str, items: list = []) -> list:
    items.append(item)
    return items

print(add_item("a"))  # ['a']
print(add_item("b"))  # ['a', 'b']  — 共享了同一个列表!

# 正确:使用None作为哨兵值
def add_item(item: str, items: list | None = None) -> list:
    if items is None:
        items = []
    items.append(item)
    return items

print(add_item("a"))  # ['a']
print(add_item("b"))  # ['b']

5.2.3 可变位置参数(*args)

python
def add(*numbers: int) -> int:
    return sum(numbers)

add(1, 2, 3)          # 6
add(1, 2, 3, 4, 5)    # 15

# 解包传递
values = [1, 2, 3, 4, 5]
add(*values)           # 等价于 add(1, 2, 3, 4, 5)

5.2.4 可变关键字参数(**kwargs)

python
def create_profile(**kwargs) -> dict:
    return kwargs

create_profile(name="Alice", age=25, city="北京")
# {'name': 'Alice', 'age': 25, 'city': '北京'}

# 解包传递
info = {"name": "Alice", "age": 25}
create_profile(**info, city="北京")

5.2.5 仅位置参数(Python 3.8+)

python
# / 左侧的参数只能按位置传递
def greet(name: str, /, message: str = "你好!") -> str:
    return f"{name}, {message}"

greet("Alice")              # 合法
greet("Alice", "欢迎!")     # 合法
greet(name="Alice")         # TypeError!name是仅位置参数

5.2.6 仅关键字参数

python
# * 右侧的参数只能按关键字传递
def greet(*, name: str, message: str = "你好!") -> str:
    return f"{name}, {message}"

greet(name="Alice")         # 合法
greet("Alice")              # TypeError!name是仅关键字参数

5.2.7 参数完整顺序

python
def complete_example(
    a, b,              # 1. 仅位置参数
    /,
    c, d=10,           # 2. 位置或关键字参数
    *,
    e=20, f,           # 3. 仅关键字参数
    **kwargs           # 4. 可变关键字参数
) -> None:
    print(f"a={a}, b={b}, c={c}, d={d}, e={e}, f={f}")
    print(f"kwargs={kwargs}")

complete_example(1, 2, 3, e=30, f=40, x=50)

5.3 作用域与闭包

5.3.1 LEGB规则

Python变量查找遵循LEGB顺序:Local → Enclosing → Global → Built-in

python
x = "global"

def outer():
    x = "enclosing"
    
    def inner():
        x = "local"
        print(x)       # "local"
    
    inner()
    print(x)           # "enclosing"

outer()
print(x)               # "global"

5.3.2 global与nonlocal

python
# global:声明使用全局变量
count = 0

def increment() -> None:
    global count
    count += 1

increment()
print(count)  # 1

# nonlocal:声明使用外层函数变量
def counter() -> Callable:
    count = 0
    
    def increment() -> int:
        nonlocal count
        count += 1
        return count
    
    return increment

c = counter()
print(c())  # 1
print(c())  # 2
print(c())  # 3

5.3.3 闭包

闭包是一个函数对象,它记住了定义时所在作用域的变量:

python
def make_multiplier(factor: float) -> Callable[[float], float]:
    """创建乘法器"""
    def multiplier(x: float) -> float:
        return x * factor
    return multiplier

double = make_multiplier(2)
triple = make_multiplier(3)

print(double(5))   # 10
print(triple(5))   # 15

# 闭包的内部状态
print(double.__closure__[0].cell_contents)  # 2

# 实用:移动平均
def make_averager() -> Callable[[float], float]:
    total = 0.0
    count = 0
    
    def averager(new_value: float) -> float:
        nonlocal total, count
        total += new_value
        count += 1
        return total / count
    
    return averager

avg = make_averager()
print(avg(10))  # 10.0
print(avg(20))  # 15.0
print(avg(30))  # 20.0

学术注记:闭包的本质是函数与其**词法环境(Lexical Environment)**的组合。Python闭包通过__closure__属性捕获外层变量,存储为cell对象。闭包是函数式编程的核心概念,也是装饰器的实现基础。


5.4 递归

5.4.1 递归基础

python
def factorial(n: int) -> int:
    """阶乘:n! = n × (n-1)!"""
    if n <= 1:
        return 1
    return n * factorial(n - 1)

def fibonacci(n: int) -> int:
    """斐波那契数列(朴素递归,指数级复杂度)"""
    if n <= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

# 汉诺塔
def hanoi(n: int, source: str, target: str, auxiliary: str) -> None:
    if n == 1:
        print(f"移动圆盘从 {source}{target}")
        return
    hanoi(n - 1, source, auxiliary, target)
    print(f"移动圆盘从 {source}{target}")
    hanoi(n - 1, auxiliary, target, source)

5.4.2 递归优化

python
from functools import lru_cache

# 记忆化:缓存计算结果
@lru_cache(maxsize=None)
def fibonacci(n: int) -> int:
    if n <= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(100))  # 瞬间完成

# 尾递归优化(Python不原生支持,但可手动转换)
def factorial_tail(n: int, acc: int = 1) -> int:
    if n <= 1:
        return acc
    return factorial_tail(n - 1, n * acc)

学术注记:Python默认递归深度限制为1000(sys.getrecursionlimit())。CPython不实现尾调用优化(TCO),因为TCO会丢失调试栈帧信息。对于深度递归,应改用迭代或显式栈结构。


5.5 高阶函数与函数式编程

5.5.1 函数作为一等公民

python
# 函数赋值给变量
greet = print
greet("Hello")

# 函数存储在容器中
operations = {
    "add": lambda a, b: a + b,
    "sub": lambda a, b: a - b,
    "mul": lambda a, b: a * b,
}
print(operations["add"](3, 5))  # 8

# 函数作为参数
def apply(x: int, y: int, func: Callable[[int, int], int]) -> int:
    return func(x, y)

# 函数作为返回值
def create_validator(min_val: int, max_val: int) -> Callable[[int], bool]:
    def validate(value: int) -> bool:
        return min_val <= value <= max_val
    return validate

is_valid_age = create_validator(0, 150)
print(is_valid_age(25))   # True

5.5.2 map、filter、reduce

python
from functools import reduce

numbers = [1, 2, 3, 4, 5]

# map:映射变换
squared = list(map(lambda x: x ** 2, numbers))
# 推荐替代:列表推导式
squared = [x ** 2 for x in numbers]

# filter:过滤
evens = list(filter(lambda x: x % 2 == 0, numbers))
# 推荐替代:列表推导式
evens = [x for x in numbers if x % 2 == 0]

# reduce:归约
total = reduce(lambda x, y: x + y, numbers)
# 推荐替代:内置函数
total = sum(numbers)

# map + filter组合
result = list(map(str, filter(lambda x: x % 2 == 0, range(10))))

工程实践:对于简单场景,列表推导式比map/filter更Pythonic。map/filter在需要链式操作大量数据时仍有优势,因为它们返回迭代器,内存效率更高。

5.5.3 lambda表达式

python
# lambda:单行匿名函数
square = lambda x: x ** 2

# 典型应用:排序的key函数
students = [{"name": "Alice", "score": 85}, {"name": "Bob", "score": 92}]
sorted_students = sorted(students, key=lambda s: s["score"])

# 多参数lambda
multiply = lambda x, y: x * y

# lambda的限制:只能包含表达式,不能包含语句
# 错误:lambda中不能使用赋值、if语句等
# lambda x: if x > 0: x else: -x  # SyntaxError
# 正确:使用条件表达式
abs_value = lambda x: x if x >= 0 else -x

工程实践:lambda应保持简短(单行)。如果逻辑复杂,应定义命名函数。PEP 8建议:不要将lambda赋值给变量——应使用def定义命名函数。

5.5.4 函数式编程进阶

偏函数(Partial Function)

python
from functools import partial

def power(base: int, exponent: int) -> int:
    return base ** exponent

square = partial(power, exponent=2)
cube = partial(power, exponent=3)

print(square(5))  # 25
print(cube(3))    # 27

# 实用场景:固定配置参数
import json
json_dump = partial(json.dumps, indent=2, ensure_ascii=False)
print(json_dump({"name": "张三", "age": 25}))

函数组合(Function Composition)

python
from functools import reduce

def compose(*functions):
    """从右向左组合函数"""
    def inner(arg):
        return reduce(lambda acc, f: f(acc), reversed(functions), arg)
    return inner

def add_one(x: int) -> int:
    return x + 1

def double(x: int) -> int:
    return x * 2

def square(x: int) -> int:
    return x ** 2

composed = compose(add_one, double, square)
print(composed(3))  # (3² * 2) + 1 = 19

# 管道操作(从左向右)
def pipe(*functions):
    def inner(arg):
        return reduce(lambda acc, f: f(acc), functions, arg)
    return inner

piped = pipe(square, double, add_one)
print(piped(3))  # ((3²) * 2) + 1 = 19

柯里化(Currying)

python
from functools import wraps

def curry(func):
    """将多参数函数转换为柯里化函数"""
    @wraps(func)
    def wrapper(*args):
        if len(args) >= func.__code__.co_argcount:
            return func(*args)
        return lambda *more: wrapper(*(args + more))
    return wrapper

@curry
def add(a: int, b: int, c: int) -> int:
    return a + b + c

print(add(1)(2)(3))    # 6
print(add(1, 2)(3))    # 6
print(add(1)(2, 3))    # 6
print(add(1, 2, 3))    # 6

不可变数据结构

python
from dataclasses import dataclass
from typing import TypeVar, Generic

T = TypeVar('T')

@dataclass(frozen=True)
class ImmutableList(Generic[T]):
    """不可变列表的函数式实现"""
    _items: tuple
    
    @classmethod
    def from_list(cls, items: list) -> "ImmutableList":
        return cls(tuple(items))
    
    def cons(self, item: T) -> "ImmutableList[T]":
        """在头部添加元素,返回新列表"""
        return ImmutableList((item,) + self._items)
    
    def head(self) -> T:
        return self._items[0] if self._items else None
    
    def tail(self) -> "ImmutableList[T]":
        return ImmutableList(self._items[1:]) if len(self._items) > 1 else ImmutableList(())
    
    def map(self, func) -> "ImmutableList":
        return ImmutableList(tuple(func(x) for x in self._items))
    
    def filter(self, pred) -> "ImmutableList":
        return ImmutableList(tuple(x for x in self._items if pred(x)))
    
    def reduce(self, func, initial=None):
        from functools import reduce
        return reduce(func, self._items, initial) if initial is not None else reduce(func, self._items)
    
    def to_list(self) -> list:
        return list(self._items)

lst = ImmutableList.from_list([1, 2, 3, 4, 5])
print(lst.filter(lambda x: x % 2 == 0).map(lambda x: x ** 2).to_list())

学术注记:函数式编程强调纯函数(无副作用)、不可变数据和函数组合。Python虽然不是纯函数式语言,但支持函数式范式。在并发编程中,不可变数据天然线程安全,无需锁机制。


5.6 装饰器

5.6.1 装饰器基础

装饰器是接受函数并返回新函数的高阶函数,用于在不修改原函数代码的情况下扩展其行为:

python
import time
from functools import wraps

def timer(func: Callable) -> Callable:
    """计时装饰器"""
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        elapsed = time.perf_counter() - start
        print(f"{func.__name__} 执行时间:{elapsed:.4f}秒")
        return result
    return wrapper

@timer
def slow_function() -> str:
    time.sleep(1)
    return "完成"

slow_function()

工程实践:始终使用@wraps(func)装饰wrapper函数,保留原函数的__name____doc__等元信息。不使用@wraps会导致调试和文档生成工具无法正确识别被装饰函数。

5.6.2 带参数的装饰器

python
def retry(max_attempts: int = 3, delay: float = 1.0):
    """重试装饰器工厂"""
    def decorator(func: Callable) -> Callable:
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(1, max_attempts + 1):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_attempts:
                        raise
                    print(f"第{attempt}次尝试失败:{e}{delay}秒后重试...")
                    time.sleep(delay)
        return wrapper
    return decorator

@retry(max_attempts=3, delay=0.5)
def unstable_api_call():
    import random
    if random.random() < 0.7:
        raise ConnectionError("网络错误")
    return "成功"

5.7 模块与包

5.7.1 模块导入

python
# 导入整个模块
import math
print(math.pi)

# 导入特定名称
from math import pi, sqrt

# 别名导入
import numpy as np
from datetime import datetime as dt

# 不推荐:通配符导入(命名空间污染)
from math import *

# 条件导入
try:
    import orjson as json
except ImportError:
    import json

5.7.2 自定义模块

python
# mymath.py
"""
mymath - 自定义数学工具模块

提供常用的数学计算函数。
"""

PI = 3.14159265

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

def is_prime(n: int) -> bool:
    if n < 2:
        return False
    for i in range(2, int(n ** 0.5) + 1):
        if n % i == 0:
            return False
    return True

if __name__ == "__main__":
    # 仅在直接运行时执行,被导入时不执行
    print(f"测试:add(3, 5) = {add(3, 5)}")
    print(f"测试:is_prime(17) = {is_prime(17)}")

5.7.3 包的组织

mypackage/
├── __init__.py          # 包初始化,定义公共API
├── core.py              # 核心功能
├── utils.py             # 工具函数
└── subpackage/
    ├── __init__.py
    └── module.py
python
# __init__.py - 控制包的公共API
from .core import main_function
from .utils import helper

__all__ = ["main_function", "helper"]
__version__ = "1.0.0"

5.7.4 常用标准库速览

模块用途常用函数/类
os操作系统接口getcwd(), listdir(), path.join()
sys系统相关argv, path, exit()
jsonJSON处理dumps(), loads(), dump(), load()
datetime日期时间datetime, timedelta, strftime()
collections特殊容器Counter, defaultdict, namedtuple
itertools迭代工具chain(), groupby(), combinations()
pathlib路径操作Path(), read_text(), glob()
logging日志记录getLogger(), basicConfig()
re正则表达式search(), findall(), sub()
dataclasses数据类@dataclass, field()

5.8 前沿技术动态

5.8.1 参数语法增强(PEP 570, PEP 616)

Python 3.8+引入了仅位置参数和新的字符串方法:

python
def greet(name, /, *, greeting="Hello"):
    print(f"{greeting}, {name}!")

greet("Alice")
greet("Alice", greeting="Hi")

5.8.2 类型注解增强(PEP 612, PEP 692)

python
from typing import ParamSpec, TypeVar, Concatenate

P = ParamSpec('P')
R = TypeVar('R')

def decorator(f: Callable[P, R]) -> Callable[P, R]:
    def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
        return f(*args, **kwargs)
    return wrapper

@decorator
def func(x: int, y: str) -> bool:
    return x > 0

5.8.3 函数性能优化

python
import functools

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

@functools.lru_cache(maxsize=128)
def expensive_computation(n: int) -> int:
    return sum(i * i for i in range(n))

5.8.4 现代模块管理

python
from importlib import resources
from importlib.metadata import version, entry_points

print(version("requests"))

eps = entry_points()
if "console_scripts" in eps:
    for ep in eps["console_scripts"]:
        print(f"Command: {ep.name} -> {ep.value}")

5.9 本章小结

本章系统介绍了Python函数与模块的完整体系:

  1. 参数体系:位置参数、关键字参数、默认参数、可变参数、仅位置/仅关键字参数
  2. 作用域:LEGB规则、global/nonlocal声明、闭包原理
  3. 递归:基础递归模式、记忆化优化(lru_cache)、尾递归转换
  4. 高阶函数:函数作为一等公民、map/filter/reduce、lambda表达式
  5. 函数式编程:偏函数、函数组合、柯里化、不可变数据结构
  6. 装饰器:函数装饰器、带参数装饰器、@wraps保留元信息
  7. 模块与包:导入机制、__name__守卫、包组织规范

5.9.1 函数设计原则

原则说明示例
单一职责一个函数只做一件事calculate_tax()而非calculate_and_print_tax()
参数最少化参数不超过3-4个,否则使用对象封装create_user(user_data)而非create_user(name, age, email, ...)
无副作用纯函数优先,避免修改输入参数返回新列表而非修改原列表
有意义的命名函数名应描述其行为get_active_users()而非get_users()
类型注解公共API必须有类型注解def add(a: int, b: int) -> int:

5.9.2 模块设计模式

模式1:工具模块

python
# string_utils.py
"""字符串工具函数集合"""

def reverse(s: str) -> str:
    """反转字符串"""
    return s[::-1]

def word_count(s: str) -> int:
    """统计单词数"""
    return len(s.split())

__all__ = ["reverse", "word_count"]

模式2:类模块

python
# database.py
"""数据库连接模块"""

class Database:
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance
    
    def connect(self):
        pass

database = Database()

模式3:配置模块

python
# config.py
"""应用配置"""
from dataclasses import dataclass

@dataclass(frozen=True)
class Config:
    DEBUG: bool = False
    DATABASE_URL: str = "sqlite:///app.db"
    SECRET_KEY: str = "dev-key"

config = Config()

模式4:工厂模块

python
# factory.py
"""对象工厂模块"""
from typing import TypeVar, Callable, Dict

T = TypeVar('T')

class Factory:
    def __init__(self):
        self._registry: Dict[str, Callable] = {}
    
    def register(self, name: str) -> Callable:
        def decorator(cls: T) -> T:
            self._registry[name] = cls
            return cls
        return decorator
    
    def create(self, name: str, *args, **kwargs) -> T:
        return self._registry[name](*args, **kwargs)

factory = Factory()

5.10 练习题

基础题

  1. 编写函数max_of_three(a, b, c),返回三个数中的最大值,不使用内置max函数。

  2. 编写递归函数计算列表元素之和。

  3. 编写函数接受可变参数,返回所有参数的平均值。

进阶题

  1. 实现装饰器@count_calls,记录被装饰函数的调用次数,可通过func.count访问。

  2. 使用闭包实现一个带状态的计数器,支持increment()decrement()reset()get_count()操作。

  3. 创建一个string_utils.py模块,包含字符串反转、单词统计、回文判断、驼峰转换等函数,并编写__all__if __name__ == "__main__"测试。

项目实践

  1. 函数式数据处理管道:编写一个数据处理工具,要求:
    • 使用高阶函数实现数据管道(读取→清洗→转换→聚合→输出)
    • 支持链式调用:pipeline(data).filter(...).map(...).reduce(...)
    • 使用装饰器添加日志和计时功能
    • 包含完整的类型注解和Docstring
    • 打包为可复用模块

思考题

  1. 为什么Python的默认参数在函数定义时求值而非调用时求值?这一设计决策的利弊是什么?

  2. 闭包与对象都可以封装状态,二者在语义和性能上有何异同?在什么场景下应选择哪种方式?

  3. Python的map/filter与列表推导式相比,各自的优势是什么?为什么Python社区更偏好列表推导式?

5.11 延伸阅读

5.11.1 函数式编程

  • 《函数式编程思维》 (Neal Ford) — 函数式编程入门经典,适合OOP背景开发者
  • 《Haskell趣学指南》 (Miran Lipovača) — 纯函数式语言学习,深入理解函数式范式
  • PEP 484 — Type Hints (https://peps.python.org/pep-0484/) — Python类型注解的官方规范
  • mypy文档 (https://mypy.readthedocs.io/) — Python静态类型检查器使用指南

5.11.2 装饰器与元编程

5.11.3 模块与包设计

5.11.4 软件设计原则

  • 《Clean Code》 (Robert C. Martin) — 函数设计原则和代码整洁之道
  • 《代码大全》 (Steve McConnell) — 软件构建的百科全书,函数设计章节必读
  • SOLID原则 — 面向对象设计的五大原则,函数设计同样适用

下一章:第6章 列表与元组

青少年创意编程 - 高中Python组 - 江苏省宿城中等专业学校