第5章 函数与模块 学习目标 完成本章学习后,读者将能够:
掌握Python函数参数的完整体系(位置参数、关键字参数、默认参数、可变参数、仅位置/仅关键字参数) 理解LEGB作用域规则与闭包的实现原理 运用递归与记忆化技术解决算法问题 理解高阶函数与函数式编程范式 掌握模块与包的组织规范,编写可复用的Python库 5.1 函数基础 5.1.1 函数定义与调用 1 2 3 4 5 6 7 8 9 10 11 12 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) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 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 类型注解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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) def process (value: Union [int , str ] ) -> str : return str (value) 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 位置参数与关键字参数 1 2 3 4 5 6 7 8 9 10 11 def greet (name: str , message: str ) -> str : return f"{name} , {message} " greet("Alice" , "你好!" ) greet(message="你好!" , name="Alice" ) greet("Alice" , message="你好!" )
5.2.2 默认参数 1 2 3 4 5 6 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 ) create_user("Charlie" , city="上海" )
关键陷阱 :默认参数在函数定义时求值一次,而非每次调用时求值。可变默认参数会导致状态共享:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def add_item (item: str , items: list = [] ) -> list : items.append(item) return items print (add_item("a" )) print (add_item("b" )) def add_item (item: str , items: list | None = None ) -> list : if items is None : items = [] items.append(item) return items print (add_item("a" )) print (add_item("b" ))
5.2.3 可变位置参数(*args) 1 2 3 4 5 6 7 8 9 def add (*numbers: int ) -> int : return sum (numbers) add(1 , 2 , 3 ) add(1 , 2 , 3 , 4 , 5 ) values = [1 , 2 , 3 , 4 , 5 ] add(*values)
5.2.4 可变关键字参数(**kwargs) 1 2 3 4 5 6 7 8 9 def create_profile (**kwargs ) -> dict : return kwargs create_profile(name="Alice" , age=25 , city="北京" ) info = {"name" : "Alice" , "age" : 25 } create_profile(**info, city="北京" )
5.2.5 仅位置参数(Python 3.8+) 1 2 3 4 5 6 7 def greet (name: str , /, message: str = "你好!" ) -> str : return f"{name} , {message} " greet("Alice" ) greet("Alice" , "欢迎!" ) greet(name="Alice" )
5.2.6 仅关键字参数 1 2 3 4 5 6 def greet (*, name: str , message: str = "你好!" ) -> str : return f"{name} , {message} " greet(name="Alice" ) greet("Alice" )
5.2.7 参数完整顺序 1 2 3 4 5 6 7 8 9 10 11 12 def complete_example ( a, b, /, c, d=10 , *, e=20 , f, **kwargs ) -> 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 x = "global" def outer (): x = "enclosing" def inner (): x = "local" print (x) inner() print (x) outer() print (x)
5.3.2 global与nonlocal 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 count = 0 def increment () -> None : global count count += 1 increment() print (count) def counter () -> Callable : count = 0 def increment () -> int : nonlocal count count += 1 return count return increment c = counter() print (c()) print (c()) print (c())
5.3.3 闭包 闭包是一个函数对象,它记住了定义时所在作用域的变量:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 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 )) print (triple(5 )) print (double.__closure__[0 ].cell_contents) 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 )) print (avg(20 )) print (avg(30 ))
学术注记 :闭包的本质是函数与其**词法环境(Lexical Environment)**的组合。Python闭包通过__closure__属性捕获外层变量,存储为cell对象。闭包是函数式编程的核心概念,也是装饰器的实现基础。
5.4 递归 5.4.1 递归基础 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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 递归优化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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 )) 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 函数作为一等公民 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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 )) 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 ))
5.5.2 map、filter、reduce 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from functools import reducenumbers = [1 , 2 , 3 , 4 , 5 ] squared = list (map (lambda x: x ** 2 , numbers)) squared = [x ** 2 for x in numbers] evens = list (filter (lambda x: x % 2 == 0 , numbers)) evens = [x for x in numbers if x % 2 == 0 ] total = reduce(lambda x, y: x + y, numbers) total = sum (numbers) result = list (map (str , filter (lambda x: x % 2 == 0 , range (10 ))))
工程实践 :对于简单场景,列表推导式比map/filter更Pythonic。map/filter在需要链式操作大量数据时仍有优势,因为它们返回迭代器,内存效率更高。
5.5.3 lambda表达式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 square = lambda x: x ** 2 students = [{"name" : "Alice" , "score" : 85 }, {"name" : "Bob" , "score" : 92 }] sorted_students = sorted (students, key=lambda s: s["score" ]) multiply = lambda x, y: x * y abs_value = lambda x: x if x >= 0 else -x
工程实践 :lambda应保持简短(单行)。如果逻辑复杂,应定义命名函数。PEP 8建议:不要将lambda赋值给变量——应使用def定义命名函数。
5.5.4 函数式编程进阶 偏函数(Partial Function)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from functools import partialdef power (base: int , exponent: int ) -> int : return base ** exponent square = partial(power, exponent=2 ) cube = partial(power, exponent=3 ) print (square(5 )) print (cube(3 )) import jsonjson_dump = partial(json.dumps, indent=2 , ensure_ascii=False ) print (json_dump({"name" : "张三" , "age" : 25 }))
函数组合(Function Composition)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 from functools import reducedef 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 )) 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 ))
柯里化(Currying)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from functools import wrapsdef 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 )) print (add(1 , 2 )(3 )) print (add(1 )(2 , 3 )) print (add(1 , 2 , 3 ))
不可变数据结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 from dataclasses import dataclassfrom 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 装饰器基础 装饰器是接受函数并返回新函数的高阶函数,用于在不修改原函数代码的情况下扩展其行为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import timefrom functools import wrapsdef 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:.4 f} 秒" ) return result return wrapper @timer def slow_function () -> str : time.sleep(1 ) return "完成" slow_function()
工程实践 :始终使用@wraps(func)装饰wrapper函数,保留原函数的__name__、__doc__等元信息。不使用@wraps会导致调试和文档生成工具无法正确识别被装饰函数。
5.6.2 带参数的装饰器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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 模块导入 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import mathprint (math.pi)from math import pi, sqrtimport numpy as npfrom datetime import datetime as dtfrom math import *try : import orjson as json except ImportError: import json
5.7.2 自定义模块 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 """ 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 包的组织 1 2 3 4 5 6 7 mypackage/ ├── __init__.py # 包初始化,定义公共API ├── core.py # 核心功能 ├── utils.py # 工具函数 └── subpackage/ ├── __init__.py └── module.py
1 2 3 4 5 6 from .core import main_functionfrom .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, namedtupleitertools迭代工具 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+引入了仅位置参数和新的字符串方法:
1 2 3 4 5 def greet (name, /, *, greeting="Hello" ): print (f"{greeting} , {name} !" ) greet("Alice" ) greet("Alice" , greeting="Hi" )
5.8.2 类型注解增强(PEP 612, PEP 692) 1 2 3 4 5 6 7 8 9 10 11 12 13 from typing import ParamSpec, TypeVar, ConcatenateP = 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 函数性能优化 1 2 3 4 5 6 7 8 9 10 11 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 现代模块管理 1 2 3 4 5 6 7 8 9 from importlib import resourcesfrom importlib.metadata import version, entry_pointsprint (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:工具模块
1 2 3 4 5 6 7 8 9 10 11 12 """字符串工具函数集合""" def reverse (s: str ) -> str : """反转字符串""" return s[::-1 ] def word_count (s: str ) -> int : """统计单词数""" return len (s.split()) __all__ = ["reverse" , "word_count" ]
模式2:类模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 """数据库连接模块""" 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:配置模块
1 2 3 4 5 6 7 8 9 10 11 """应用配置""" 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:工厂模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 """对象工厂模块""" 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 函数式编程 5.11.2 装饰器与元编程 5.11.3 模块与包设计 5.11.4 软件设计原则 《Clean Code》 (Robert C. Martin) — 函数设计原则和代码整洁之道《代码大全》 (Steve McConnell) — 软件构建的百科全书,函数设计章节必读SOLID原则 — 面向对象设计的五大原则,函数设计同样适用下一章:第6章 列表与元组