Python 装饰器:一篇就懂

45 阅读4分钟

Python 装饰器:一篇就懂

什么是装饰器?

装饰器,英文叫 Decorator,顾名思义就是"装饰"东西的工具。

它的作用是:在不修改原函数代码的前提下,给函数额外加点功能

比如你想给一个函数加个计时、加个日志、加个权限校验,但又不想改动函数本身的代码——装饰器就是干这个的。

两个前置概念

在学装饰器之前,先了解两个 Python 的特性。

1. 函数是一等对象

在 Python 里,函数和普通变量一样,可以:

  • 赋值给变量
  • 作为参数传给另一个函数
  • 在函数内部定义
def hello():
    return "你好"

# 函数可以赋值给变量
func = hello
print(func())  # 输出:你好

2. 嵌套函数

函数内部可以定义函数:

def outer():
    def inner():
        return "我是内部函数"
    return inner()

print(outer())  # 输出:我是内部函数

这两个特性是装饰器的基础。

从需求出发

假设你写了一个函数:

import time

def calculate():
    """计算 1+2+3+...+1000000"""
    total = 0
    for i in range(1000000):
        total += i
    print(f"结果是 {total}")

现在你想测量这个函数运行花了多长时间

方法一:直接改函数(不推荐)

def calculate():
    start = time.time()
    total = 0
    for i in range(1000000):
        total += i
    print(f"结果是 {total}")
    end = time.time()
    print(f"耗时 {end - start:.4f} 秒")

问题:如果你有很多函数都想计时,每个都加同样的计时代码?太麻烦了。

方法二:用一个包装函数

import time

def timer(func):
    """计时包装器"""
    def wrapper():
        start = time.time()
        func()       # 调用原函数
        end = time.time()
        print(f"耗时 {end - start:.4f} 秒")
    return wrapper

def calculate():
    total = 0
    for i in range(1000000):
        total += i
    print(f"结果是 {total}")

# 用 wrapper 包装 calculate
calculate = timer(calculate)
calculate()  # 输出结果 + 耗时

这个 timer 函数就是个装饰器——它"装饰"了 calculate,加了计时功能。

方法三:用 @ 语法糖(推荐)

上面的 calculate = timer(calculate) 可以简写为:

import time

def timer(func):
    def wrapper():
        start = time.time()
        func()
        end = time.time()
        print(f"耗时 {end - start:.4f} 秒")
    return wrapper

@timer
def calculate():
    total = 0
    for i in range(1000000):
        total += i
    print(f"结果是 {total}")

calculate()  # 调用时自动包装

@timer 就是 calculate = timer(calculate) 的简写。

装饰器的标准模板

绝大多数装饰器都长这样:

import functools

def my_decorator(func):
    @functools.wraps(func)  # 保留原函数的名字和文档
    def wrapper(*args, **kwargs):
        # 调用原函数之前做的事
        print("调用前")

        result = func(*args, **kwargs)  # 调用原函数

        # 调用原函数之后做的事
        print("调用后")

        return result

    return wrapper

关键点:

  • *args, **kwargs:接收任意参数,这样装饰的函数不管有什么参数都能正常工作
  • functools.wraps:保留原函数的 __name____doc__ 等信息
  • wrapperreturn 原函数的返回值

实际例子

例子 1:计时器

import time
import functools

def timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} 耗时 {end - start:.4f} 秒")
        return result
    return wrapper

@timer
def add():
    total = sum(range(1000000))
    print(f"结果是 {total}")

add()

例子 2:日志记录

import functools

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"[LOG] 调用函数 {func.__name__},参数 args={args}, kwargs={kwargs}")
        result = func(*args, **kwargs)
        print(f"[LOG] {func.__name__} 返回 {result}")
        return result
    return wrapper

@log
def greet(name, greeting="你好"):
    return f"{greeting}, {name}!"

greet("小明", greeting="早上好")

例子 3:带参数的装饰器

有时候装饰器本身也需要参数,比如你想控制日志的级别:

import functools

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(3)
def say_hello():
    print("你好!")

say_hello()  # 输出三遍"你好!"

三层结构:

  • 最外层 repeat(times):接收装饰器参数
  • 中间层 decorator(func):接收被装饰的函数
  • 最内层 wrapper:实际包装逻辑

例子 4:多个装饰器叠加

@log
@timer
def add(a, b):
    return a + b

# 等价于:add = log(timer(add))
# 叠加(应用)顺序:从下到上,先 timer 再 log
# 调用执行顺序:从外到内,先 log → timer → add

多个装饰器叠加时,应用顺序从下到上,调用执行顺序从外到内。

总结

要点说明
本质一个接收函数、返回新函数的普通函数
@ 语法@decorator 等价于 func = decorator(func)
wrapper接收 *args, **kwargs,调用原函数并返回结果
@functools.wraps保留原函数的名字、文档等信息
多个装饰器从下到上叠加,从里到外执行

装饰器看起来绕,但核心思想就一句话:用一个函数去包装另一个函数,在不改原函数代码的情况下加功能