Python 装饰器 (Decorators) 是一种高级的 Python 语法糖,它允许你在不修改原始函数定义的情况下,增强或修改函数的功能。装饰器本质上是一个 Python 函数,它接收一个函数作为参数,并返回一个修改后或增强后的新函数。它们是实现“开闭原则”(对扩展开放,对修改关闭)的重要工具,常用于日志记录、性能测试、事务处理、权限验证等场景,属于面向切面编程 (AOP) 的范畴。

核心思想:装饰器是“函数套函数”的语法糖,通过闭包的特性,在不改变被装饰函数代码的情况下,为其添加预处理、后处理或其他功能。


一、理解装饰器前的预备知识

要真正理解装饰器,我们需要先掌握几个 Python 核心概念:

1.1 函数是第一类对象 (First-Class Objects)

在 Python 中,函数与其他数据类型(如整数、字符串)一样,是第一类对象。这意味着你可以:

  • 将函数赋值给变量
  • 将函数作为参数传递给其他函数
  • 将函数作为另一个函数的返回值
  • 在数据结构中存储函数

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def greet(name):
return f"Hello, {name}!"

# 赋值给变量
say_hello = greet
print(say_hello("Alice")) # Output: Hello, Alice!

# 作为参数传递
def execute_func(func, arg):
return func(arg)

print(execute_func(greet, "Bob")) # Output: Hello, Bob!

# 作为返回值
def get_multiplier(factor):
def multiplier(number):
return number * factor
return multiplier

double = get_multiplier(2)
print(double(5)) # Output: 10

1.2 闭包 (Closures)

当一个内层函数引用了外层函数作用域中的变量,即使外层函数执行完毕,内层函数仍然能访问这些变量,这种现象就称为闭包。

示例

1
2
3
4
5
6
7
8
9
10
11
def outer_function(msg):
# msg 是 outer_function 的局部变量

def inner_function():
# inner_function 引用了外层函数的 msg 变量
print(msg)
return inner_function # 返回 inner_function,但它仍然“记住”了 msg

my_func = outer_function("Hello from closure!")
my_func() # Output: Hello from closure!
# 此时 outer_function 已经执行完毕,但 my_func 仍然可以访问 msg

闭包是装饰器实现其功能的基础。

二、装饰器的基本语法与工作原理

2.1 装饰器的定义

装饰器函数通常接受一个函数作为参数,并返回一个新的函数(通常是内层包裹函数)。

1
2
3
4
5
6
7
def my_decorator(func): # 装饰器函数,接受一个函数 func
def wrapper(*args, **kwargs): # 包裹函数,会替代原函数执行
print("Something is happening before the function is called.")
result = func(*args, **kwargs) # 调用原函数
print("Something is happening after the function is called.")
return result
return wrapper # 返回新的函数(wrapper)

2.2 使用 @ 语法糖

Python 提供了 @ 语法糖,使得使用装饰器更加简洁和直观。

1
2
3
4
5
6
7
8
9
10
11
@my_decorator
def say_hello(name):
print(f"Hello, {name}!")
return "Done"

# 等价于:
# say_hello = my_decorator(say_hello)

# 调用被装饰的函数
result = say_hello("Alice")
print(result)

输出

1
2
3
4
Something is happening before the function is called.
Hello, Alice!
Something is happening after the function is called.
Done

工作原理

  1. 当 Python 解释器看到 @my_decorator 时,它会执行 my_decorator(say_hello)
  2. my_decorator 函数接收 say_hello 函数作为参数 func
  3. my_decorator 定义并返回了一个新的 wrapper 函数。
  4. 最终,say_hello 这个名字不再指向原始的 say_hello 函数,而是指向 my_decorator 返回的 wrapper 函数。
  5. 当调用 say_hello("Alice") 时,实际执行的是 wrapper("Alice")wrapper 函数在其内部再调用原始的 say_hello 函数。

2.3 functools.wraps

当函数被装饰后,它的元信息(如 __name__, __doc__, __module__ 等)会丢失,变成装饰器内部 wrapper 函数的元信息。这在调试和使用一些工具时可能会造成混淆。

为了解决这个问题,我们可以使用 functools 模块中的 wraps 装饰器来“复制”原函数的元信息到包裹函数上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from functools import wraps

def my_decorator_with_wraps(func):
@wraps(func) # 使用 wraps 装饰器
def wrapper(*args, **kwargs):
print("Something is happening before the function is called.")
result = func(*args, **kwargs)
print("Something is happening after the function is called.")
return result
return wrapper

@my_decorator_with_wraps
def greet_with_name(name):
"""Greets the given name."""
print(f"Hello, {name}!")

print(greet_with_name.__name__) # Output: greet_with_name (而不是 wrapper)
print(greet_with_name.__doc__) # Output: Greets the given name.

三、带参数的装饰器

有时候,我们需要在定义装饰器时传入参数,来控制装饰器的行为。这需要一层额外的函数嵌套。

3.1 定义带参数的装饰器

一个带参数的装饰器是一个工厂函数,它接收参数并返回一个真正的装饰器函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def repeat(num_times): # 外部工厂函数,接收装饰器的参数
def decorator(func): # 真正的装饰器函数,接收被装饰的函数
@wraps(func)
def wrapper(*args, **kwargs): # 包裹函数
for _ in range(num_times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator # 工厂函数返回装饰器函数

@repeat(num_times=3) # 调用工厂函数,返回 decorator,再用 decorator 装饰 greet
def greet(name):
print(f"Hello, {name}!")

greet("Alice")

输出

1
2
3
Hello, Alice!
Hello, Alice!
Hello, Alice!

工作原理

  1. repeat(num_times=3) 被调用时,它返回 decorator 这个函数。
  2. 然后,@decorator 等价于 @repeat(num_times=3) 的结果,它会用返回的 decorator 函数来装饰 greet
  3. 后面的工作原理与不带参数的装饰器相同。

四、类装饰器 (Class Decorators)

除了函数,类也可以作为装饰器。类装饰器主要通过实现 __call__ 方法使其成为可调用的对象。

4.1 类装饰器的定义与使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class MyClassDecorator:
def __init__(self, func):
self.func = func
wraps(func)(self) # 同样可以使用 wraps 拷贝元信息

def __call__(self, *args, **kwargs):
print(f"Class decorator: Before calling {self.func.__name__}")
result = self.func(*args, **kwargs)
print(f"Class decorator: After calling {self.func.__name__}")
return result

@MyClassDecorator
def say_hi(name):
print(f"Hi, {name}!")
return "Finished"

result = say_hi("Bob")
print(result)

print(say_hi.__name__) # Output: say_hi

输出

1
2
3
4
5
Class decorator: Before calling say_hi
Hi, Bob!
Class decorator: After calling say_hi
Finished
say_hi

4.2 带参数的类装饰器

类装饰器也可以带参数,同样需要一个外层工厂函数来接收参数并返回一个类实例,或者利用类的 __init____call__ 的配合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class LogDecorator:
def __init__(self, level="INFO"): # 接收装饰器参数
self.level = level

def __call__(self, func): # 真正的装饰器部分,接收被装饰函数
@wraps(func)
def wrapper(*args, **kwargs):
print(f"[{self.level}] Calling {func.__name__}...")
result = func(*args, **kwargs)
print(f"[{self.level}] {func.__name__} finished.")
return result
return wrapper

@LogDecorator(level="DEBUG")
def calculate(a, b):
return a + b

print(calculate(10, 20))

输出

1
2
3
[DEBUG] Calling calculate...
[DEBUG] calculate finished.
30

五、装饰器的应用场景

5.1 记录日志 (Logging)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from functools import wraps
import logging

def log_calls(func):
@wraps(func)
def wrapper(*args, **kwargs):
logging.info(f"Calling function {func.__name__} with args: {args}, kwargs: {kwargs}")
result = func(*args, **kwargs)
logging.info(f"Function {func.__name__} returned: {result}")
return result
return wrapper

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

logging.basicConfig(level=logging.INFO)
add(5, 3)

5.2 性能测试 (Timing)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from functools import wraps
import time

def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
start_time = time.perf_counter()
result = func(*args, **kwargs)
end_time = time.perf_counter()
print(f"Function {func.__name__} took {end_time - start_time:.4f} seconds.")
return result
return wrapper

@timer
def long_running_function():
time.sleep(2)
return "Done sleeping"

long_running_function()

5.3 权限验证 (Authorization)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from functools import wraps

def requires_role(role):
def decorator(func):
@wraps(func)
def wrapper(user, *args, **kwargs):
if user.has_role(role):
return func(user, *args, **kwargs)
else:
raise PermissionError(f"User {user.username} does not have '{role}' role.")
return wrapper
return decorator

class User:
def __init__(self, username, roles):
self.username = username
self.roles = roles

def has_role(self, role):
return role in self.roles

@requires_role("admin")
def delete_data(user, item_id):
print(f"User {user.username} deleted item {item_id}.")
return True

admin_user = User("Alice", ["admin"])
guest_user = User("Bob", ["guest"])

try:
delete_data(admin_user, 123)
delete_data(guest_user, 456)
except PermissionError as e:
print(e)

5.4 缓存 (Caching)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from functools import wraps

def cache_result(func):
_cache = {} # 存储函数结果的字典
@wraps(func)
def wrapper(*args): # 简化为只处理位置参数,实际需要更复杂的hash for kwargs
if args not in _cache:
_cache[args] = func(*args)
return _cache[args]
return wrapper

@cache_result
def expensive_calculation(a, b):
print(f"Calculating {a} + {b}...")
time.sleep(1) # 模拟耗时操作
return a + b

print(expensive_calculation(1, 2)) # 第一次计算
print(expensive_calculation(1, 2)) # 第二次直接从缓存获取
print(expensive_calculation(3, 4)) # 第一次计算

注意:Python 标准库提供了更强大的 @functools.lru_cache 装饰器用于缓存。

六、多个装饰器的叠加

一个函数可以被多个装饰器装饰。装饰器的应用顺序是从下到上,但执行顺序是从外到内。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def deco_a(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("------- Deco A Start -------")
result = func(*args, **kwargs)
print("------- Deco A End -------")
return result
return wrapper

def deco_b(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("+++++++ Deco B Start +++++++")
result = func(*args, **kwargs)
print("+++++++ Deco B End +++++++")
return result
return wrapper

@deco_a
@deco_b
def my_function():
print("This is the original function.")

my_function()

输出

1
2
3
4
5
------- Deco A Start -------
+++++++ Deco B Start +++++++
This is the original function.
+++++++ Deco B End +++++++
------- Deco A End -------

理解顺序

  1. Python 解释器首先看到 @deco_bmy_function = deco_b(my_function)。此时 my_function 变成了 deco_b 返回的 wrapper
  2. 然后,解释器看到 @deco_amy_function = deco_a(my_function)。此时 my_function 变成了 deco_a 返回的 wrapper(这个 wrapper 里面包裹着 deco_b 返回的 wrapper)。
  3. 当调用 my_function() 时,最外层的 deco_awrapper 先执行,它会调用其包裹的函数(也就是 deco_bwrapper),deco_bwrapper 再调用原始的 my_function。所以执行顺序是 A -> B -> 原函数 -> B -> A。

七、总结

Python 装饰器是实现函数功能扩展和行为修改的强大工具。其核心原理是利用函数的第一类对象特性和闭包。掌握装饰器能够让你编写更优雅、更具可读性和可维护性的代码,同时也是理解许多 Python 优秀库(如 Flask、Django)工作方式的关键。

  • 装饰器函数:接受一个函数,返回一个新函数。
  • @ 语法糖:简化了装饰器的应用。
  • functools.wraps:保留原函数的元信息。
  • 带参数的装饰器:通过额外的函数嵌套实现。
  • 类装饰器:通过 __init____call__ 方法实现。
  • 应用场景:日志、性能监控、权限控制、缓存等。
  • 叠加顺序:从下到上应用,从外到内执行。

理解并熟练运用装饰器,将极大地提升你的 Python 编程能力。