1.2. 函数进阶
Python 函数是一等公民,理解其高级特性对于写出优雅代码至关重要。
1.2.1. 参数传递机制
1.2.1.1. 易错点:传值 vs 传引用
Python 使用"传对象引用"(pass by object reference),理解这一点很重要:
def modify_list(lst):
lst.append(4) # 修改原对象
lst = [1, 2, 3, 4, 5] # 重新绑定局部变量,不影响原对象
original = [1, 2, 3]
modify_list(original)
print(original) # [1, 2, 3, 4] - append 生效,重新赋值无效
def modify_number(n):
n = n + 1 # 创建新对象,不影响原变量
x = 10
modify_number(x)
print(x) # 10 - 不变
警告
可变默认参数陷阱是 Python 最著名的陷阱之一:
# ❌ 错误示例
def append_to(element, to=[]):
to.append(element)
return to
print(append_to(1)) # [1]
print(append_to(2)) # [1, 2] - 不是 [2]!
print(append_to(3)) # [1, 2, 3]
# ✅ 正确做法
def append_to_fixed(element, to=None):
if to is None:
to = []
to.append(element)
return to
1.2.2. *args 和 **kwargs
1.2.2.1. 位置参数与关键字参数
def flexible_func(required, *args, keyword_only, **kwargs):
"""
参数顺序:
1. 普通参数
2. *args(收集额外位置参数)
3. keyword-only 参数(*args 之后的参数)
4. **kwargs(收集额外关键字参数)
"""
print(f"required: {required}")
print(f"args: {args}")
print(f"keyword_only: {keyword_only}")
print(f"kwargs: {kwargs}")
flexible_func(1, 2, 3, 4, keyword_only="must specify", extra="value")
# required: 1
# args: (2, 3, 4)
# keyword_only: must specify
# kwargs: {'extra': 'value'}
1.2.2.2. 仅位置参数(Python 3.8+)
def pos_only(x, y, /, z):
"""x 和 y 只能通过位置传递"""
return x + y + z
pos_only(1, 2, 3) # ✅
pos_only(1, 2, z=3) # ✅
# pos_only(x=1, y=2, z=3) # ❌ TypeError
# 实际应用:避免参数名变更破坏 API
def compute(x, y, /, *, precision=2):
"""x, y 位置参数;precision 关键字参数"""
return round(x / y, precision)
1.2.2.3. 参数解包
def point(x, y, z):
return f"({x}, {y}, {z})"
# 列表/元组解包
coords = [1, 2, 3]
print(point(*coords)) # (1, 2, 3)
# 字典解包
params = {'x': 1, 'y': 2, 'z': 3}
print(point(**params)) # (1, 2, 3)
# 合并字典(Python 3.9+)
defaults = {'a': 1, 'b': 2}
overrides = {'b': 3, 'c': 4}
combined = {**defaults, **overrides} # {'a': 1, 'b': 3, 'c': 4}
# 或使用 | 运算符
combined = defaults | overrides # Python 3.9+
1.2.3. 闭包
闭包是函数式编程的基础,理解它对掌握装饰器至关重要。
1.2.3.1. 闭包原理
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(c2()) # 1 - 独立的计数器
# 查看闭包变量
print(c1.__closure__[0].cell_contents) # 2
1.2.3.2. 易错点:循环中的闭包
# ❌ 常见错误
funcs = []
for i in range(3):
funcs.append(lambda: i)
print([f() for f in funcs]) # [2, 2, 2] - 都是 2!
# ✅ 解决方案1:默认参数捕获
funcs = []
for i in range(3):
funcs.append(lambda i=i: i) # i=i 捕获当前值
print([f() for f in funcs]) # [0, 1, 2]
# ✅ 解决方案2:使用 functools.partial
from functools import partial
funcs = [partial(lambda x: x, i) for i in range(3)]
print([f() for f in funcs]) # [0, 1, 2]
1.2.4. 装饰器
1.2.4.1. 基本装饰器
import functools
import time
def timer(func):
"""测量函数执行时间的装饰器"""
@functools.wraps(func) # 保留原函数的元信息
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
end = time.perf_counter()
print(f"{func.__name__} took {end - start:.4f}s")
return result
return wrapper
@timer
def slow_function(n):
"""一个慢函数"""
time.sleep(n)
return n
slow_function(1) # slow_function took 1.0012s
# 保留了原函数的信息
print(slow_function.__name__) # slow_function
print(slow_function.__doc__) # 一个慢函数
重要
始终使用 @functools.wraps 装饰 wrapper 函数,否则原函数的 __name__、__doc__ 等属性会丢失。
1.2.4.2. 带参数的装饰器
def retry(max_attempts=3, delay=1):
"""带重试功能的装饰器"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
last_exception = None
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
last_exception = e
print(f"Attempt {attempt + 1} failed: {e}")
if attempt < max_attempts - 1:
time.sleep(delay)
raise last_exception
return wrapper
return decorator
@retry(max_attempts=3, delay=0.5)
def unstable_api_call():
import random
if random.random() < 0.7:
raise ConnectionError("Network error")
return "Success"
1.2.4.3. 类装饰器
class CountCalls:
"""统计函数调用次数的类装饰器"""
def __init__(self, func):
functools.update_wrapper(self, func)
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
return self.func(*args, **kwargs)
@CountCalls
def greet(name):
return f"Hello, {name}!"
print(greet("Alice")) # Hello, Alice!
print(greet("Bob")) # Hello, Bob!
print(greet.count) # 2
1.2.4.4. 装饰器执行顺序
def decorator_a(func):
print("Applying decorator A")
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("Before A")
result = func(*args, **kwargs)
print("After A")
return result
return wrapper
def decorator_b(func):
print("Applying decorator B")
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("Before B")
result = func(*args, **kwargs)
print("After B")
return result
return wrapper
@decorator_a
@decorator_b
def hello():
print("Hello!")
# 应用顺序:从下到上
# Applying decorator B
# Applying decorator A
hello()
# 调用顺序:从外到内
# Before A
# Before B
# Hello!
# After B
# After A
1.2.5. 函数式编程
1.2.5.1. map, filter, reduce
from functools import reduce
numbers = [1, 2, 3, 4, 5]
# map: 对每个元素应用函数
squared = list(map(lambda x: x**2, numbers))
# 更 Pythonic: [x**2 for x in numbers]
# filter: 过滤元素
evens = list(filter(lambda x: x % 2 == 0, numbers))
# 更 Pythonic: [x for x in numbers if x % 2 == 0]
# reduce: 累积计算
total = reduce(lambda acc, x: acc + x, numbers, 0)
# 更 Pythonic: sum(numbers)
# reduce 的实际应用场景
data = [{'name': 'Alice', 'score': 85},
{'name': 'Bob', 'score': 92},
{'name': 'Charlie', 'score': 78}]
# 找出最高分
highest = reduce(
lambda best, x: x if x['score'] > best['score'] else best,
data
)
1.2.5.2. 偏函数
from functools import partial
def power(base, exponent):
return base ** exponent
# 创建平方函数
square = partial(power, exponent=2)
# 创建立方函数
cube = partial(power, exponent=3)
print(square(5)) # 25
print(cube(3)) # 27
# 实际应用:配置 API 调用
import urllib.request
def fetch_url(url, timeout=10, headers=None):
req = urllib.request.Request(url, headers=headers or {})
return urllib.request.urlopen(req, timeout=timeout)
# 创建预配置的函数
api_fetch = partial(
fetch_url,
timeout=30,
headers={'Authorization': 'Bearer token'}
)
1.2.5.3. operator 模块
from operator import attrgetter, itemgetter, methodcaller
# itemgetter: 获取字典/序列的项
students = [
{'name': 'Alice', 'grade': 85, 'age': 20},
{'name': 'Bob', 'grade': 92, 'age': 19},
{'name': 'Charlie', 'grade': 78, 'age': 21},
]
# 按成绩排序
by_grade = sorted(students, key=itemgetter('grade'))
# 多字段排序
by_grade_age = sorted(students, key=itemgetter('grade', 'age'))
# attrgetter: 获取对象属性
class Student:
def __init__(self, name, grade):
self.name = name
self.grade = grade
students = [Student('Alice', 85), Student('Bob', 92)]
by_grade = sorted(students, key=attrgetter('grade'))
# methodcaller: 调用方法
names = ['alice', 'bob', 'charlie']
upper_names = list(map(methodcaller('upper'), names))
# ['ALICE', 'BOB', 'CHARLIE']
1.2.6. 最佳实践
函数设计原则
单一职责:一个函数只做一件事
合理的参数数量:超过 5 个考虑使用配置对象
避免可变默认参数:使用
None作为默认值返回一致的类型:不要返回
None或值混用
装饰器最佳实践
始终使用
@functools.wraps保持装饰器简单,复杂逻辑抽取到辅助函数
考虑支持同步和异步函数
提供清晰的文档和示例