函数、作用域与闭包
参数绑定
python
def request(path, /, method="GET", *, timeout=3, **headers):
...| 标记 | 含义 |
|---|---|
/ 之前 | 仅限位置参数 |
/ 与 * 之间 | 位置或关键字参数 |
* 之后 | 仅限关键字参数 |
*args | 收集额外位置参数为 Tuple |
**kwargs | 收集额外关键字参数为 Dict |
仅限关键字参数适合超时、重试等容易因位置混淆的选项;仅限位置参数允许库将来修改内部参数名而不破坏调用方。
可变默认参数
默认值在函数定义时求值一次,不是每次调用创建:
python
def add_item(item, bucket=[]):
bucket.append(item)
return bucket
add_item(1) # [1]
add_item(2) # [1, 2]正确写法:
python
def add_item(item, bucket=None):
if bucket is None:
bucket = []
bucket.append(item)
return bucket缓存等少数场景可以故意利用默认对象持久存在,但应显式使用闭包或缓存工具表达意图。
传对象引用
Python 调用时把对象绑定给形参:
python
def mutate(values):
values.append(3) # 修改调用方看到的同一对象
def rebind(values):
values = [3] # 只改变局部名字绑定这既不是传统描述中的“按值复制整个对象”,也不是允许函数重绑调用方变量的“按引用传参”。判断影响要看操作是修改对象还是重新绑定名字。
LEGB 与 nonlocal
名字按 Local、Enclosing、Global、Builtins 顺序查找。函数内只要存在赋值,该名字默认被视为局部变量:
python
count = 0
def increment():
count += 1 # UnboundLocalError修改模块变量使用 global;修改闭包外层函数变量使用 nonlocal。更容易维护的方式通常是返回新值或把状态封装进对象。
闭包晚绑定
闭包读取的是变量,不是创建函数时的值:
python
funcs = [lambda: i for i in range(3)]
print([f() for f in funcs]) # [2, 2, 2]需要冻结当前值时使用默认参数:
python
funcs = [lambda i=i: i for i in range(3)]装饰器
python
@trace
@retry(times=3)
def load():
...等价于:
python
load = trace(retry(times=3)(load))装饰从下往上应用,调用时从外往内进入。包装器应使用 functools.wraps 保留 __name__、文档和签名元数据。
python
from functools import wraps
def trace(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(func.__name__)
return func(*args, **kwargs)
return wrapperlambda 的边界
lambda 只能包含一个表达式,适合短小的 Key Function 或回调:
python
users.sort(key=lambda user: (user.age, user.name))复杂逻辑使用 def,以获得名称、类型标注、文档和可调试的中间步骤。把异常处理、副作用和嵌套条件塞进 Lambda 并不会更函数式。