阅读时长:约35分钟
前言
函数是编程中最重要的抽象机制之一。它将一段可复用的代码封装起来,赋予其名称和参数接口,使得复杂的程序可以被分解为可管理的小模块。本文将深入探讨Python函数的方方面面,从基础定义到高级特性,从内存机制到性能优化,帮助你全面掌握Python函数编程。
一、函数基础
1.1 函数定义与调用
# 基础函数定义def greet(name): """向用户问好""" return f"你好,{name}!"# 函数调用message = greet("张三")print(message) # 输出: 你好,张三!# 多返回值函数(实际是返回元组)def get_min_max(numbers): """返回列表中的最小值和最大值""" return min(numbers), max(numbers)min_val, max_val = get_min_max([3, 1, 4, 1, 5, 9])print(f"最小值: {min_val}, 最大值: {max_val}")
1.2 文档字符串(Docstring)
良好的文档是高质量代码的标志:
def calculate_area(length, width): """ 计算矩形的面积。 参数: length (float): 矩形的长度 width (float): 矩形的宽度 返回: float: 矩形的面积 示例: >>> calculate_area(5, 3) 15 """ return length * width# 查看文档print(calculate_area.__doc__)help(calculate_area)
二、参数类型详解
2.1 位置参数与关键字参数
def create_user(name, age, city="北京"): """创建用户信息""" return {"name": name, "age": age, "city": city}# 位置参数user1 = create_user("张三", 25)# 关键字参数(顺序无关)user2 = create_user(age=30, name="李四")# 混合使用(位置参数必须在关键字参数之前)user3 = create_user("王五", city="上海", age=35)
2.2 默认参数值
from datetime import datetimedef log_message(message, level="INFO", timestamp=None): """记录日志消息""" if timestamp is None: timestamp = datetime.now() return f"[{timestamp}] {level}: {message}"# 使用默认参数print(log_message("系统启动"))# 覆盖默认参数print(log_message("发生错误", level="ERROR"))
⚠️ 重要警告:默认参数在函数定义时求值,而非调用时
# ❌ 错误:使用可变对象作为默认参数def add_item(item, item_list=[]): item_list.append(item) return item_listprint(add_item(1)) # [1]print(add_item(2)) # [1, 2] - 意外!列表被共享了# ✅ 正确:使用None作为默认值def add_item_safe(item, item_list=None): if item_list is None: item_list = [] item_list.append(item) return item_listprint(add_item_safe(1)) # [1]print(add_item_safe(2)) # [2] - 正确
2.3 可变参数:*args 和 **kwargs
# *args 接收任意数量的位置参数def sum_all(*args): """求和所有参数""" return sum(args)print(sum_all(1, 2, 3)) # 6print(sum_all()) # 0print(sum_all(1, 2, 3, 4, 5)) # 15# **kwargs 接收任意数量的关键字参数def print_info(**kwargs): """打印所有关键字参数""" for key, value in kwargs.items(): print(f"{key}: {value}")print_info(name="张三", age=25, city="北京")# 组合使用def flexible_function(required, default="value", *args, **kwargs): """展示所有参数类型的组合""" print(f"必需参数: {required}") print(f"默认参数: {default}") print(f"可变位置参数: {args}") print(f"可变关键字参数: {kwargs}")flexible_function("必须值", "自定义", 1, 2, 3, extra="附加信息")
2.4 参数解包
def calculate(x, y, z): return x + y * z# 列表/元组解包data = [1, 2, 3]result = calculate(*data) # 等价于 calculate(1, 2, 3)# 字典解包params = {"x": 1, "y": 2, "z": 3}result = calculate(**params) # 等价于 calculate(x=1, y=2, z=3)# 组合解包args = [1]kwargs = {"y": 2, "z": 3}result = calculate(*args, **kwargs)
三、作用域与LEGB规则
3.1 命名空间与作用域
# 全局变量global_var = "我是全局变量"def outer_function(): # 局部变量( enclosing 作用域) outer_var = "我是外部函数变量" def inner_function(): # 局部变量( local 作用域) inner_var = "我是内部函数变量" print(inner_var) # 访问局部变量 print(outer_var) # 访问 enclosing 变量 print(global_var) # 访问全局变量 inner_function()outer_function()
3.2 LEGB规则详解
Python查找变量的顺序遵循LEGB规则:
# 演示LEGB规则len = "我覆盖了内置的len" # Globaldef demo(): len = "我是局部变量" # Local print(len) # 输出: 我是局部变量demo()print(len) # 输出: 我覆盖了内置的len# 删除全局覆盖,恢复内置函数del lenprint(len([1, 2, 3])) # 输出: 3
3.3 global 和 nonlocal 关键字
counter = 0 # 全局变量def increment_global(): global counter # 声明使用全局变量 counter += 1 return counterdef outer(): count = 0 # enclosing变量 def inner(): nonlocal count # 声明使用enclosing变量 count += 1 return count return inner# 测试increment_global()print(counter) # 1inner_func = outer()print(inner_func()) # 1print(inner_func()) # 2
四、函数是一等公民
4.1 函数作为对象
在Python中,函数是一等公民(First-Class Citizen),这意味着:
def multiply(x, y): return x * y# 函数赋值给变量operation = multiplyprint(operation(3, 4)) # 12# 函数存储在字典中operations = { "add": lambda x, y: x + y, "subtract": lambda x, y: x - y, "multiply": multiply, "divide": lambda x, y: x / y if y != 0 else "除数不能为0"}def calculate(a, b, op): """使用函数作为参数""" return operations.get(op, lambda x, y: "未知操作")(a, b)print(calculate(10, 5, "add")) # 15print(calculate(10, 5, "multiply")) # 50
4.2 高阶函数
def apply_operation(numbers, operation): """高阶函数:接收函数作为参数""" return [operation(n) for n in numbers]def make_multiplier(factor): """高阶函数:返回函数""" def multiplier(x): return x * factor return multiplier# 使用示例double = make_multiplier(2)triple = make_multiplier(3)print(double(5)) # 10print(triple(5)) # 15numbers = [1, 2, 3, 4, 5]squares = apply_operation(numbers, lambda x: x ** 2)print(squares) # [1, 4, 9, 16, 25]
4.3 内置高阶函数
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]# map: 对每个元素应用函数squares = list(map(lambda x: x ** 2, numbers))# filter: 根据条件过滤evens = list(filter(lambda x: x % 2 == 0, numbers))# sorted: 自定义排序words = ["banana", "pie", "Washington", "book"]sorted_by_length = sorted(words, key=len)# reduce: 累积计算from functools import reduceproduct = reduce(lambda x, y: x * y, numbers)print(f"平方: {squares}")print(f"偶数: {evens}")print(f"按长度排序: {sorted_by_length}")print(f"乘积: {product}")
五、闭包与装饰器
5.1 闭包(Closure)
闭包是指引用了外部作用域变量的函数,即使外部函数已经返回,闭包仍然可以访问这些变量。
def make_counter(): """创建一个计数器闭包""" count = 0 # 自由变量 def counter(): nonlocal count count += 1 return count return counter# 创建两个独立的计数器counter_a = make_counter()counter_b = make_counter()print(counter_a()) # 1print(counter_a()) # 2print(counter_b()) # 1(独立的计数器)print(counter_a()) # 3
闭包的内存机制:
- 闭包会捕获外部函数的局部变量,即使外部函数执行完毕,这些变量也不会被垃圾回收
counter = make_counter()print(counter.__closure__) # 查看闭包捕获的变量print(counter.__closure__[0].cell_contents) # 查看变量的值
5.2 装饰器(Decorator)
装饰器是一种用于修改或增强函数行为的高阶函数。
import functoolsimport timedef timing_decorator(func): """计算函数执行时间的装饰器""" @functools.wraps(func) # 保留原函数元数据 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@timing_decoratordef slow_function(): """一个慢函数""" time.sleep(1) return "Done"# 等价于: slow_function = timing_decorator(slow_function)result = slow_function()
5.3 带参数的装饰器
def repeat(times): """带参数的装饰器:重复执行函数""" def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): for _ in range(times): result = func(*args, **kwargs) return result return wrapper return decorator@repeat(times=3)def greet(name): print(f"你好, {name}!")greet("张三") # 会打印3次
六、递归函数
6.1 递归基础
def factorial(n): """阶乘的递归实现""" if n <= 1: return 1 return n * factorial(n - 1)def fibonacci(n): """斐波那契数列的递归实现""" if n <= 1: return n return fibonacci(n - 1) + fibonacci(n - 2)print(f"5! = {factorial(5)}") # 120print(f"fib(10) = {fibonacci(10)}") # 55
6.2 递归深度限制
Python默认的递归深度限制为1000:
import sysprint(f"默认递归深度限制: {sys.getrecursionlimit()}")# 修改递归深度限制(谨慎使用)sys.setrecursionlimit(2000)
为什么会有递归深度限制?
递归深度限制的存在是为了防止栈溢出。每次函数调用都会在调用栈上创建一个新的栈帧(Stack Frame),包含:
栈空间是有限的,无限递归会导致栈溢出,程序崩溃。
6.3 尾递归优化(有限支持)
Python解释器不支持尾递归优化,但我们可以手动优化:
# 普通递归(有栈溢出风险)def factorial_normal(n): if n <= 1: return 1 return n * factorial_normal(n - 1)# 尾递归形式(Python不优化,仅展示概念)def factorial_tail(n, accumulator=1): if n <= 1: return accumulator return factorial_tail(n - 1, n * accumulator)# 迭代版本(推荐)def factorial_iterative(n): result = 1 for i in range(2, n + 1): result *= i return result
七、函数性能优化:lru_cache
7.1 记忆化(Memoization)
from functools import lru_cache# 未优化的斐波那契(重复计算严重)def fib_slow(n): if n <= 1: return n return fib_slow(n - 1) + fib_slow(n - 2)# 使用lru_cache优化@lru_cache(maxsize=None)def fib_fast(n): if n <= 1: return n return fib_fast(n - 1) + fib_fast(n - 2)# 性能对比import timen = 35start = time.time()result1 = fib_slow(n)print(f"未优化: fib({n}) = {result1}, 耗时: {time.time() - start:.4f}秒")start = time.time()result2 = fib_fast(n)print(f"优化后: fib({n}) = {result2}, 耗时: {time.time() - start:.4f}秒")
7.2 lru_cache 原理
lru_cache使用LRU(Least Recently Used)缓存策略:
@lru_cache(maxsize=128) # 最多缓存128个结果def expensive_function(x): """模拟耗时操作""" time.sleep(0.1) return x * x# 查看缓存信息print(expensive_function.cache_info())# CacheInfo(hits=0, misses=0, maxsize=128, currsize=0)expensive_function(5)print(expensive_function.cache_info())# CacheInfo(hits=0, misses=1, maxsize=128, currsize=1)expensive_function(5) # 命中缓存print(expensive_function.cache_info())# CacheInfo(hits=1, misses=1, maxsize=128, currsize=1)# 清空缓存expensive_function.cache_clear()
缓存策略对比:
八、实战项目
8.1 斐波那契数列的多种实现
from functools import lru_cachefrom time import time# 方法1: 纯递归(指数级复杂度)def fib_recursive(n): if n <= 1: return n return fib_recursive(n - 1) + fib_recursive(n - 2)# 方法2: 记忆化递归@lru_cache(maxsize=None)def fib_memoized(n): if n <= 1: return n return fib_memoized(n - 1) + fib_memoized(n - 2)# 方法3: 迭代(线性复杂度)def fib_iterative(n): if n <= 1: return n a, b = 0, 1 for _ in range(2, n + 1): a, b = b, a + b return b# 方法4: 矩阵快速幂(对数级复杂度)def matrix_mult(A, B): """2x2矩阵乘法""" return [ [A[0][0]*B[0][0] + A[0][1]*B[1][0], A[0][0]*B[0][1] + A[0][1]*B[1][1]], [A[1][0]*B[0][0] + A[1][1]*B[1][0], A[1][0]*B[0][1] + A[1][1]*B[1][1]] ]def matrix_pow(M, n): """矩阵快速幂""" if n == 1: return M if n % 2 == 0: half = matrix_pow(M, n // 2) return matrix_mult(half, half) else: return matrix_mult(M, matrix_pow(M, n - 1))def fib_matrix(n): if n <= 1: return n M = [[1, 1], [1, 0]] result = matrix_pow(M, n) return result[0][1]# 性能对比def benchmark(func, n, runs=5): times = [] for _ in range(runs): start = time() result = func(n) times.append(time() - start) return sum(times) / runs, resultn = 30print(f"计算第 {n} 个斐波那契数\n")for name, func in [ ("递归", fib_recursive), ("记忆化", fib_memoized), ("迭代", fib_iterative), ("矩阵", fib_matrix)]: avg_time, result = benchmark(func, n) print(f"{name:8s}: {result:10d} 平均耗时: {avg_time:.6f}秒")
8.2 简易计算器
def make_calculator(): """创建一个支持历史记录的计算器""" history = [] def calculate(operation, a, b): operations = { 'add': lambda x, y: x + y, 'subtract': lambda x, y: x - y, 'multiply': lambda x, y: x * y, 'divide': lambda x, y: x / y if y != 0 else float('inf'), 'power': lambda x, y: x ** y } if operation not in operations: raise ValueError(f"不支持的操作: {operation}") result = operations[operation](a, b) history.append(f"{operation}({a}, {b}) = {result}") return result def get_history(): return history.copy() def clear_history(): history.clear() # 返回一个包含多个函数的命名空间 return type('Calculator', (), { 'calc': calculate, 'history': get_history, 'clear': clear_history })()# 使用计算器calc = make_calculator()print(calc.calc('add', 5, 3)) # 8print(calc.calc('multiply', 4, 7)) # 28print(calc.calc('power', 2, 10)) # 1024print("\n计算历史:")for record in calc.history(): print(record)
8.3 文件批量重命名工具
import osimport refrom pathlib import Pathdef batch_rename(directory, pattern, replacement, preview=True): """ 批量重命名文件 参数: directory: 目标目录 pattern: 正则表达式模式 replacement: 替换字符串 preview: 是否仅预览(不实际执行) """ path = Path(directory) if not path.exists(): raise FileNotFoundError(f"目录不存在: {directory}") changes = [] regex = re.compile(pattern) for file_path in path.iterdir(): if file_path.is_file(): old_name = file_path.name new_name = regex.sub(replacement, old_name) if old_name != new_name: changes.append((file_path, path / new_name)) if not changes: print("没有需要重命名的文件") return print(f"\n发现 {len(changes)} 个文件需要重命名:\n") for old, new in changes: print(f" {old.name}") print(f" -> {new.name}\n") if preview: print("⚠️ 这是预览模式,使用 preview=False 执行实际重命名") return # 执行重命名 for old, new in changes: old.rename(new) print(f"✅ 成功重命名 {len(changes)} 个文件")# 使用示例# batch_rename("./photos", r"IMG_(\d+)", r"vacation_\1", preview=True)
九、常见陷阱与最佳实践
9.1 陷阱1:默认参数的坑
# ❌ 错误示例已在2.2节展示# ✅ 最佳实践:可变默认参数的处理模式def process_data(data=None): data = data or [] # 简洁写法 # 或 if data is None: data = [] # 处理数据... return data
9.2 陷阱2:延迟绑定闭包
# ❌ 错误:所有函数返回相同结果functions = []for i in range(5): functions.append(lambda: i)print([f() for f in functions]) # [4, 4, 4, 4, 4]# ✅ 正确:使用默认参数绑定当前值functions = []for i in range(5): functions.append(lambda x=i: x)print([f() for f in functions]) # [0, 1, 2, 3, 4]
9.3 陷阱3:递归没有终止条件
# ❌ 无限递归def infinite_recursion(n): return infinite_recursion(n + 1) # 没有终止条件# ✅ 始终确保有终止条件def safe_recursion(n, limit=100): if n >= limit: return n return safe_recursion(n + 1, limit)
9.4 最佳实践清单
十、本章小结
核心知识点
- 函数定义
- 参数类型:位置参数、关键字参数、默认参数、*args、**kwargs
- 作用域:LEGB规则,global和nonlocal关键字
- 一等公民
- 闭包
- 装饰器
- 递归
- 缓存
底层原理要点
- 栈帧
- 闭包内存:通过
__closure__捕获自由变量,延长生命周期 - 递归限制
- lru_cache
十一、课后练习
基础练习
参数解析器:编写一个函数,接收任意数量的位置参数和关键字参数,打印它们的值和类型
计数器工厂:使用闭包创建一个计数器工厂,每个计数器有独立的计数值
类型检查装饰器:编写一个装饰器,检查函数参数的类型是否符合注解要求
进阶练习
管道函数:实现一个管道操作符,允许 data | func1 | func2 | func3 这样的链式调用
尾递归装饰器:编写一个装饰器,将尾递归函数转换为迭代执行(绕过Python的递归限制)
函数组合:实现函数组合操作,(f ∘ g)(x) = f(g(x))
挑战练习
实现partial函数:不借助functools,自己实现partial函数
实现简单的lru_cache:不借助functools,自己实现带大小限制的缓存装饰器
参考资源
💡 学习建议:函数是编程的核心抽象工具。建议多练习高阶函数和装饰器的使用,它们是写出优雅Python代码的关键。同时要注意递归的使用场景,在Python中迭代通常比递归更高效。
本文是《Python全栈修炼之路》系列第7篇,系列文章持续更新中,欢迎关注!