Python 装饰器与函数高级用法
不接收 self 或 cls,等同于普通函数,只是命名空间归类在类里。 Python 3.10+ 起静态方法继承原函数的属性(__module__、__name__、__qualname__、__doc__、__annotations__),并新增 __wrapped__ 属性,且可直接作为普通函数调用(无需通过类或实例)。 接收 cls(类本身)而非 self(实例),可在不创建实例的情况下访问类变量。 Python 3.10+ 起类方法继承原函数的属性(__module__、__name__、__qualname__、__doc__、__annota
官方文档:https://docs.python.org/zh-cn/3/reference/compound_stmts.html#function-definitions
适用版本:Python 3.10+(2026-05-07 核实)
一、函数参数详解
参数类型全览
def func(pos1, pos2, /, normal, *, kw_only1, kw_only2=10, **kwargs):
pass
| 符号 | 含义 |
|---|---|
pos1, pos2, / |
/ 之前:仅位置参数(Python 3.8+),不能用关键字传入 |
normal |
普通参数,可位置可关键字 |
* |
* 之后:仅关键字参数,必须用关键字传入 |
*args |
收集多余位置参数为元组 |
**kwargs |
收集多余关键字参数为字典 |
# 仅位置参数(/)
def greet(name, /, greeting='Hello'):
return f'{greeting}, {name}'
greet('Alice') # OK
greet('Alice', 'Hi') # OK
greet(name='Alice') # TypeError:name 是仅位置参数
# 仅关键字参数(*)
def create_user(*, name, age, admin=False):
...
create_user(name='Alice', age=30) # OK
create_user('Alice', 30) # TypeError
*args 和 **kwargs
def func(*args, **kwargs):
print(args) # 元组
print(kwargs) # 字典
func(1, 2, 3, x=4, y=5)
# args = (1, 2, 3)
# kwargs = {'x': 4, 'y': 5}
# 解包传参
lst = [1, 2, 3]
dct = {'x': 4, 'y': 5}
func(*lst, **dct)
默认参数陷阱
# 错误:可变默认值是共享的
def append_to(item, lst=[]): # lst 只创建一次!
lst.append(item)
return lst
append_to(1) # [1]
append_to(2) # [1, 2] 不是 [2]!
# 正确:用 None 作哨兵
def append_to(item, lst=None):
if lst is None:
lst = []
lst.append(item)
return lst
二、装饰器
基础装饰器
import functools
def my_decorator(func):
@functools.wraps(func) # 保留原函数元信息(__name__, __doc__ 等)
def wrapper(*args, **kwargs):
print('前置操作')
result = func(*args, **kwargs)
print('后置操作')
return result
return wrapper
@my_decorator
def greet(name):
"""打招呼"""
return f'Hello, {name}'
# 等价于
greet = my_decorator(greet)
带参数的装饰器
def retry(max_times=3, exceptions=(Exception,)):
"""重试装饰器"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(max_times):
try:
return func(*args, **kwargs)
except exceptions as e:
if attempt == max_times - 1:
raise
print(f'第 {attempt + 1} 次失败,重试:{e}')
return wrapper
return decorator
@retry(max_times=5, exceptions=(ConnectionError, TimeoutError))
def fetch_data(url):
...
类装饰器
class Timer:
def __init__(self, func):
functools.update_wrapper(self, func) # 等价于 @wraps
self.func = func
self.call_count = 0
def __call__(self, *args, **kwargs):
import time
start = time.perf_counter()
result = self.func(*args, **kwargs)
elapsed = time.perf_counter() - start
self.call_count += 1
print(f'{self.func.__name__} 耗时 {elapsed:.4f}s,共调用 {self.call_count} 次')
return result
@Timer
def slow_func():
import time
time.sleep(0.1)
装饰类的方法
def validate_positive(func):
@functools.wraps(func)
def wrapper(self, value, *args, **kwargs):
if value <= 0:
raise ValueError(f'{value} 必须为正数')
return func(self, value, *args, **kwargs)
return wrapper
class Account:
@validate_positive
def deposit(self, amount):
self.balance += amount
多个装饰器叠加
@decorator_a
@decorator_b
@decorator_c
def func():
pass
# 等价于(从下往上包裹)
func = decorator_a(decorator_b(decorator_c(func)))
# 执行时:decorator_a.wrapper → decorator_b.wrapper → decorator_c.wrapper → func
三、常用内置装饰器
@property(见 内置函数完全参考)
@staticmethod
不接收 self 或 cls,等同于普通函数,只是命名空间归类在类里。
Python 3.10+ 起静态方法继承原函数的属性(__module__、__name__、__qualname__、__doc__、__annotations__),并新增 __wrapped__ 属性,且可直接作为普通函数调用(无需通过类或实例)。
class MathUtils:
@staticmethod
def add(a, b):
return a + b
MathUtils.add(1, 2) # 3(不需要实例)
@classmethod
接收 cls(类本身)而非 self(实例),可在不创建实例的情况下访问类变量。
Python 3.10+ 起类方法继承原函数的属性(__module__、__name__、__qualname__、__doc__、__annotations__),并新增 __wrapped__ 属性。Python 3.9+ 起类方法可包裹其他描述符(如 property)。
class Config:
_instance = None
@classmethod
def get_instance(cls):
if cls._instance is None:
cls._instance = cls()
return cls._instance
@classmethod
def from_dict(cls, data):
"""另一种构造方式"""
obj = cls()
obj.data = data
return obj
@functools.lru_cache(maxsize=128, typed=False)
| 参数 | 说明 |
|---|---|
maxsize |
缓存最大条目数;None 无限制(禁用 LRU,缓存无上限);128 为默认值 |
typed |
True 时,不同类型的相同参数(如 1 和 1.0)视为不同缓存键 |
from functools import lru_cache
@lru_cache(maxsize=256)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
fibonacci.cache_info() # CacheInfo(hits=..., misses=..., maxsize=256, currsize=...)
fibonacci.cache_clear() # 清除缓存
fibonacci.cache_parameters() # {'maxsize': 256, 'typed': False}(Python 3.9+)
fibonacci.__wrapped__ # 原始未缓存函数
Python 3.9+ 可用
@functools.cache(相当于maxsize=None的lru_cache,更轻量)。
@functools.cached_property (Python 3.8+)
计算一次后缓存到实例 __dict__,后续访问直接读属性(比 @property + lru_cache 更简洁)。
class DataProcessor:
def __init__(self, data):
self.data = data
@functools.cached_property
def result(self):
print('计算中...')
return sum(self.data) # 只计算一次
p = DataProcessor([1, 2, 3])
p.result # 计算中... → 6
p.result # 6(直接读缓存,不打印)
# 清除缓存:直接删除属性
del p.result
p.result # 重新计算
线程安全注意(Python 3.12 变更):3.12 起
cached_property移除了锁机制,在多线程环境下可能被多个线程同时计算(即 getter 被执行多次)。如果对线程安全有要求,请使用@property+lru_cache组合,或加锁保护。
@dataclasses.dataclass
from dataclasses import dataclass, field
@dataclass(order=True, frozen=False)
class Point:
x: float
y: float
z: float = 0.0
tags: list = field(default_factory=list) # 可变默认值必须用 field
p = Point(1.0, 2.0)
print(p) # Point(x=1.0, y=2.0, z=0.0, tags=[])
@dataclass 参数:
| 参数 | 默认 | 版本 | 说明 |
|---|---|---|---|
init |
True |
3.7+ | 生成 __init__ |
repr |
True |
3.7+ | 生成 __repr__ |
eq |
True |
3.7+ | 生成 __eq__(基于所有字段) |
order |
False |
3.7+ | 生成比较方法(__lt__、__le__、__gt__、__ge__);eq=False 时不能设为 True |
unsafe_hash |
False |
3.7+ | 强制生成 __hash__(即使不安全);通常用 frozen=True 代替 |
frozen |
False |
3.7+ | True 使实例不可变(字段赋值会抛 FrozenInstanceError),且自动可哈希 |
match_args |
True |
3.10+ | 生成 __match_args__,用于模式匹配(match/case) |
kw_only |
False |
3.10+ | 所有字段仅关键字传入 |
slots |
False |
3.10+ | 生成 __slots__ 节省内存(约 40% 内存减少) |
weakref_slot |
False |
3.11+ | 在 __slots__ 中添加 __weakref__ 槽,允许对实例创建弱引用(需 slots=True) |
四、闭包
闭包是引用了外层作用域变量的函数,外层函数返回后变量仍存活:
def make_counter(start=0):
count = start
def increment(step=1):
nonlocal count # 声明引用外层变量(而非创建局部变量)
count += step
return count
return increment
counter = make_counter(10)
counter() # 11
counter(5) # 16
nonlocal vs global
| 关键字 | 作用域 |
|---|---|
nonlocal x |
引用最近的外层(非全局)作用域中的 x |
global x |
引用模块全局作用域中的 x |
# 陷阱:循环变量捕获
funcs = [lambda: i for i in range(3)]
[f() for f in funcs] # [2, 2, 2] 不是 [0, 1, 2]!
# 修复:用默认参数捕获当前值
funcs = [lambda i=i: i for i in range(3)]
[f() for f in funcs] # [0, 1, 2]
五、高阶函数技巧
functools.partial(func, *args, **kwargs)
预填充部分参数,创建新的可调用对象:
from functools import partial
def power(base, exp):
return base ** exp
square = partial(power, exp=2)
cube = partial(power, exp=3)
square(5) # 25
cube(3) # 27
# 实用场景:配置特定行为的函数
import json
json_pretty = partial(json.dumps, indent=2, ensure_ascii=False)
Python 3.14+ 新增 Placeholder 支持:可预填充任意位置的参数,不限于前导位置。
from functools import partial, Placeholder as _
# 预填充第三个参数,前两个留空
say_to_world = partial(print, _, _, "world!")
say_to_world('Hello', 'dear') # Hello dear world!
# 预填充第二个参数
divide_by = partial(lambda a, b: a / b, _, 2)
divide_by(10) # 5.0
functools.reduce(function, iterable, initializer=None)
累积计算:
from functools import reduce
reduce(lambda acc, x: acc + x, [1, 2, 3, 4], 0) # 10
reduce(lambda a, b: a * b, range(1, 6)) # 120(5!)
operator 模块代替 lambda
import operator
sorted(items, key=operator.itemgetter('score')) # 比 lambda x: x['score'] 快
sorted(items, key=operator.attrgetter('name')) # 属性访问
sorted(items, key=operator.itemgetter(2, 0)) # 多字段:先索引2,再索引0
from operator import add, mul
reduce(add, [1, 2, 3]) # 6
六、异步函数
async def / await
import asyncio
async def fetch(url: str) -> str:
await asyncio.sleep(1) # 模拟 IO
return f'data from {url}'
async def main():
# 串行(共 2 秒)
result1 = await fetch('url1')
result2 = await fetch('url2')
# 并发(共 1 秒)
result1, result2 = await asyncio.gather(
fetch('url1'),
fetch('url2')
)
异步装饰器
def async_retry(max_times=3):
def decorator(func):
@functools.wraps(func)
async def wrapper(*args, **kwargs):
for attempt in range(max_times):
try:
return await func(*args, **kwargs)
except Exception as e:
if attempt == max_times - 1:
raise
await asyncio.sleep(0.1 * (attempt + 1))
return wrapper
return decorator
@async_retry(max_times=3)
async def fetch_with_retry(url):
...
异步上下文管理器
class AsyncDB:
async def __aenter__(self):
self.conn = await connect()
return self.conn
async def __aexit__(self, exc_type, exc_val, exc_tb):
await self.conn.close()
return False # 不吞噬异常
async with AsyncDB() as conn:
await conn.execute(...)
七、实用技巧
函数签名检查
import inspect
sig = inspect.signature(func)
for name, param in sig.parameters.items():
print(name, param.kind, param.default)
__slots__ 节省内存
class Point:
__slots__ = ('x', 'y') # 禁用 __dict__,节省约 40% 内存
def __init__(self, x, y):
self.x = x
self.y = y
单分派泛型函数
Python 3.7+ 起支持类型注解注册方式;Python 3.11+ 起支持 typing.Union 和 X | Y 联合类型注册。
from functools import singledispatch
@singledispatch
def process(data):
raise NotImplementedError(f'不支持类型 {type(data)}')
@process.register(str)
def _(data):
return data.upper()
@process.register(list)
def _(data):
return [process(x) for x in data]
@process.register(int)
@process.register(float)
def _(data):
return data * 2
# Python 3.11+:使用注解语法注册联合类型
@process.register
def _(data: int | float):
return data * 2
最佳实践
使用 @functools.wraps(func) 保留被装饰函数的元信息:不加 @wraps 时,装饰后函数的 __name__、__doc__、__annotations__ 会变成 wrapper 函数的属性,导致文档工具和调试信息失真。
import functools
def my_decorator(func):
@functools.wraps(func) # 必须加
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
用 *args, **kwargs 让装饰器对任意签名的函数兼容:固定参数签名的装饰器只能装饰特定签名的函数,*args, **kwargs 透传参数使装饰器通用。
装饰器需要带参数时,使用三层嵌套函数(装饰器工厂):外层函数接收参数,中层返回真正的装饰器,内层是 wrapper,逻辑清晰。
def retry(times=3, delay=1.0):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for i in range(times):
try:
return func(*args, **kwargs)
except Exception:
if i == times - 1:
raise
time.sleep(delay)
return wrapper
return decorator
@retry(times=5, delay=0.5)
def fetch_data(): ...
可变默认参数用 None 代替:def f(x, data=[]) 中默认 data 是所有调用共享的同一列表对象,应改为 def f(x, data=None): data = data or []。
使用 functools.lru_cache 缓存纯函数结果,而非手写缓存字典:@lru_cache 线程安全、自动管理 LRU 淘汰,无需手动维护缓存逻辑。
常见陷阱
陷阱:闭包变量捕获的是变量引用,而非值快照
现象: 在循环中用 lambda 或嵌套函数创建函数列表,所有函数执行时使用的都是循环结束后的最终值。
原因: Python 闭包捕获的是变量本身(引用),不是创建闭包时的值。循环变量 i 在所有闭包中共享同一个绑定。
解决: 用默认参数将当前值"固化":lambda i=i: i,或用 functools.partial。
# 错误:所有函数都打印 9
funcs = [lambda: i for i in range(10)]
print(funcs[0]()) # 9,而非 0
# 正确:用默认参数固化当前值
funcs = [lambda i=i: i for i in range(10)]
print(funcs[0]()) # 0
陷阱:可变默认参数在多次调用间共享
现象: 函数多次调用后,默认列表参数积累了之前调用的数据,产生意外输出。
原因: 函数定义时,默认参数值只求值一次,列表/字典对象在所有调用间共享。
解决: 默认值改为 None,函数体内创建新对象。
# 错误
def append(item, lst=[]):
lst.append(item)
return lst
# 正确
def append(item, lst=None):
if lst is None:
lst = []
lst.append(item)
return lst
陷阱:装饰器未加 @wraps 导致 __name__ 变为 wrapper
现象: pytest 无法识别测试函数名,Flask 路由注册报"视图函数重名",日志中函数名全是 wrapper。
原因: 没有 @functools.wraps(func),装饰后的函数对象的 __name__ 是内部 wrapper 函数的名称。
解决: 始终在 wrapper 函数上添加 @functools.wraps(func)。