项目里到处都是重复的日志代码,每个函数开头写 logger.info("开始..."),结尾写 logger.info("结束")。后来用装饰器统一处理,代码简洁了很多。这篇文章记录一下装饰器的实战用法。

什么是闭包

装饰器的基础是闭包。简单说,闭包就是函数里定义的函数,能记住外层函数的变量。

def outer(x):
    def inner(y):
        return x + y  # inner 记住了 x 的值
    return inner

add5 = outer(5)
print(add5(3))  # 输出 8
print(add5(10))  # 输出 15

坑在于:outer(5) 执行完后,按理说 x 应该被销毁了。但 inner 函数记住了 x 的值,所以还能用。

最简单的装饰器

def log_decorator(func):
    def wrapper(*args, **kwargs):
        print(f"调用 {func.__name__}")
        result = func(*args, **kwargs)
        print(f"{func.__name__} 执行完成")
        return result
    return wrapper

@log_decorator
def add(a, b):
    return a + b

add(1, 2)
# 输出:
# 调用 add
# add 执行完成

@log_decorator 等价于 add = log_decorator(add)

带参数的装饰器

如果装饰器本身需要参数,要再包一层:

import time
from functools import wraps

def timer(threshold=None):
    def decorator(func):
        @wraps(func)  # 保留原函数的 __name__ 和 __doc__
        def wrapper(*args, **kwargs):
            start = time.time()
            result = func(*args, **kwargs)
            elapsed = time.time() - start
            if threshold and elapsed > threshold:
                print(f"警告:{func.__name__} 耗时 {elapsed:.2f}s,超过阈值 {threshold}s")
            else:
                print(f"{func.__name__} 耗时 {elapsed:.2f}s")
            return result
        return wrapper
    return decorator

@timer(threshold=1)
def slow_function():
    time.sleep(2)

slow_function()
# 输出:警告:slow_function 耗时 2.00s,超过阈值 1s

坑在于:不加 @wraps(func),装饰后的函数 __name__ 会变成 wrapper,调试时很困惑。

实战:日志装饰器

import logging
import time
from functools import wraps

logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)

def log_execution(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        logger.info(f"开始执行 {func.__name__},参数: args={args}, kwargs={kwargs}")
        start = time.time()
        try:
            result = func(*args, **kwargs)
            elapsed = time.time() - start
            logger.info(f"{func.__name__} 执行成功,耗时: {elapsed:.3f}s")
            return result
        except Exception as e:
            elapsed = time.time() - start
            logger.error(f"{func.__name__} 执行失败,耗时: {elapsed:.3f}s,错误: {e}")
            raise
    return wrapper

@log_execution
def process_data(data):
    time.sleep(0.5)
    return [x * 2 for x in data]

result = process_data([1, 2, 3])

输出:

2026-07-20 14:30:00 [INFO] 开始执行 process_data,参数: args=([1, 2, 3],), kwargs={}
2026-07-20 14:30:01 [INFO] process_data 执行成功,耗时: 0.501s

实战:重试装饰器

import time
from functools import wraps

def retry(max_retries=3, delay=1):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(max_retries):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_retries - 1:
                        raise
                    print(f"第 {attempt + 1} 次失败:{e},{delay}s 后重试...")
                    time.sleep(delay)
        return wrapper
    return decorator

@retry(max_retries=3, delay=2)
def unstable_api():
    import random
    if random.random() < 0.7:
        raise ConnectionError("API 连接失败")
    return "成功"

result = unstable_api()

这个装饰器在网络请求场景很有用,自动重试失败的请求。

实战:缓存装饰器

from functools import wraps

def cache(func):
    cached = {}
    @wraps(func)
    def wrapper(*args):
        if args in cached:
            print(f"命中缓存:{args}")
            return cached[args]
        result = func(*args)
        cached[args] = result
        return result
    return wrapper

@cache
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(10))  # 计算
print(fibonacci(10))  # 命中缓存

坑在于:这个简单缓存没有过期机制,如果数据量大会占用内存。Python 3.9+ 可以用 functools.lru_cache

实战:权限检查装饰器

from functools import wraps

def require_permission(permission):
    def decorator(func):
        @wraps(func)
        def wrapper(user, *args, **kwargs):
            if permission not in user.get("permissions", []):
                raise PermissionError(f"用户 {user['name']} 没有 {permission} 权限")
            return func(user, *args, **kwargs)
        return wrapper
    return decorator

@require_permission("admin")
def delete_user(user, user_id):
    print(f"删除用户 {user_id}")

admin = {"name": "admin", "permissions": ["admin", "read"]}
user = {"name": "guest", "permissions": ["read"]}

delete_user(admin, 123)  # 正常执行
# delete_user(user, 123)  # 抛出 PermissionError

踩坑总结

  1. 装饰器本质是闭包,记住外层函数的变量
  2. @wraps(func) 保留原函数的元信息
  3. 带参数的装饰器要再包一层
  4. 装饰器顺序从下往上执行
  5. Python 3.9+ 用 functools.lru_cache 做缓存,比自己实现更可靠

print() 调试到装饰器统一处理,只需要花半小时学习一次,但能省下无数重复代码。

标签: Python

添加新评论