第5章 函数与模块
学习目标
完成本章学习后,读者将能够:
- 掌握Python函数参数的完整体系(位置参数、关键字参数、默认参数、可变参数、仅位置/仅关键字参数)
- 理解LEGB作用域规则与闭包的实现原理
- 运用递归与记忆化技术解决算法问题
- 理解高阶函数与函数式编程范式
- 掌握模块与包的组织规范,编写可复用的Python库
5.1 函数基础
5.1.1 函数定义与调用
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)
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 类型注解
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 位置参数与关键字参数
def greet(name: str, message: str) -> str:
return f"{name}, {message}"
# 位置参数:按顺序传递
greet("Alice", "你好!")
# 关键字参数:按名称传递,顺序无关
greet(message="你好!", name="Alice")
# 混合使用:位置参数在前,关键字参数在后
greet("Alice", message="你好!")5.2.2 默认参数
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关键陷阱:默认参数在函数定义时求值一次,而非每次调用时求值。可变默认参数会导致状态共享:
# 错误:可变默认参数
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)
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)
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+)
# / 左侧的参数只能按位置传递
def greet(name: str, /, message: str = "你好!") -> str:
return f"{name}, {message}"
greet("Alice") # 合法
greet("Alice", "欢迎!") # 合法
greet(name="Alice") # TypeError!name是仅位置参数5.2.6 仅关键字参数
# * 右侧的参数只能按关键字传递
def greet(*, name: str, message: str = "你好!") -> str:
return f"{name}, {message}"
greet(name="Alice") # 合法
greet("Alice") # TypeError!name是仅关键字参数5.2.7 参数完整顺序
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
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
# 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()) # 35.3.3 闭包
闭包是一个函数对象,它记住了定义时所在作用域的变量:
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 递归基础
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 递归优化
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 函数作为一等公民
# 函数赋值给变量
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)) # True5.5.2 map、filter、reduce
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表达式
# 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)
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)
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)
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不可变数据结构
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 装饰器基础
装饰器是接受函数并返回新函数的高阶函数,用于在不修改原函数代码的情况下扩展其行为:
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 带参数的装饰器
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 模块导入
# 导入整个模块
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 json5.7.2 自定义模块
# 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# __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() |
json | JSON处理 | 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+引入了仅位置参数和新的字符串方法:
def greet(name, /, *, greeting="Hello"):
print(f"{greeting}, {name}!")
greet("Alice")
greet("Alice", greeting="Hi")5.8.2 类型注解增强(PEP 612, PEP 692)
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 > 05.8.3 函数性能优化
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 现代模块管理
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函数与模块的完整体系:
- 参数体系:位置参数、关键字参数、默认参数、可变参数、仅位置/仅关键字参数
- 作用域:LEGB规则、global/nonlocal声明、闭包原理
- 递归:基础递归模式、记忆化优化(lru_cache)、尾递归转换
- 高阶函数:函数作为一等公民、map/filter/reduce、lambda表达式
- 函数式编程:偏函数、函数组合、柯里化、不可变数据结构
- 装饰器:函数装饰器、带参数装饰器、@wraps保留元信息
- 模块与包:导入机制、
__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:工具模块
# 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:类模块
# 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:配置模块
# 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:工厂模块
# 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 练习题
基础题
编写函数
max_of_three(a, b, c),返回三个数中的最大值,不使用内置max函数。编写递归函数计算列表元素之和。
编写函数接受可变参数,返回所有参数的平均值。
进阶题
实现装饰器
@count_calls,记录被装饰函数的调用次数,可通过func.count访问。使用闭包实现一个带状态的计数器,支持
increment()、decrement()、reset()和get_count()操作。创建一个
string_utils.py模块,包含字符串反转、单词统计、回文判断、驼峰转换等函数,并编写__all__和if __name__ == "__main__"测试。
项目实践
- 函数式数据处理管道:编写一个数据处理工具,要求:
- 使用高阶函数实现数据管道(读取→清洗→转换→聚合→输出)
- 支持链式调用:
pipeline(data).filter(...).map(...).reduce(...) - 使用装饰器添加日志和计时功能
- 包含完整的类型注解和Docstring
- 打包为可复用模块
思考题
为什么Python的默认参数在函数定义时求值而非调用时求值?这一设计决策的利弊是什么?
闭包与对象都可以封装状态,二者在语义和性能上有何异同?在什么场景下应选择哪种方式?
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 装饰器与元编程
- PEP 318 — Decorators for Functions and Methods (https://peps.python.org/pep-0318/) — 装饰器语法提案
- 《Python Cookbook》第9章 — 元编程技术详解
- functools模块文档 (https://docs.python.org/3/library/functools.html) — 高阶函数工具集
5.11.3 模块与包设计
- 《The Hitchhiker's Guide to Python》 — Python项目结构和打包最佳实践
- Python Packaging User Guide (https://packaging.python.org/) — 官方打包指南
- PEP 420 — Implicit Namespace Packages (https://peps.python.org/pep-0420/) — 隐式命名空间包
5.11.4 软件设计原则
- 《Clean Code》 (Robert C. Martin) — 函数设计原则和代码整洁之道
- 《代码大全》 (Steve McConnell) — 软件构建的百科全书,函数设计章节必读
- SOLID原则 — 面向对象设计的五大原则,函数设计同样适用
下一章:第6章 列表与元组