用 Python 装饰器简化代码(使用方法与实例) – wiki基地


解锁Python代码的魔法:深入理解与应用装饰器简化开发

引言

在 Python 的世界里,优雅、简洁且可读性强的代码是开发者们共同追求的目标。随着项目的复杂度不断增加,我们常常会遇到一些需要在多个函数或方法中重复执行的任务,比如日志记录、性能计时、访问控制、输入验证等等。如果简单地复制粘贴这些代码块,不仅会让代码显得臃肿、难以维护,还容易引入错误。

有没有一种更“Pythonic”的方式来处理这些横切关注点(cross-cutting concerns)呢?答案就是 装饰器(Decorators)

Python 装饰器是一种强大且独特的语法糖,它允许你在不修改原函数或类的情况下,给它们“附加”新的功能。它提供了一种干净、优雅的方式来包装函数或方法,从而实现代码的复用和简化。对于任何希望写出更高效、更可维护 Python 代码的开发者来说,理解和掌握装饰器都是至关重要的。

本文将带你深入探索 Python 装饰器的世界。我们将从为什么要使用装饰器开始,逐步讲解其底层原理,并通过丰富的实例演示如何编写和应用不同类型的装饰器,包括带参数的装饰器、装饰类方法以及如何使用 functools.wraps 保留原始函数的元数据。读完本文,你将能够自如地运用装饰器来简化你的 Python 代码,提升开发效率。

1. 为什么要使用装饰器?——痛点与解决方案

想象一下,你正在开发一个 Web 应用,需要在用户访问某些视图函数(view functions)之前检查他们是否已登录。一种直观的做法可能是在每个需要登录验证的函数开头都加上类似的代码:

“`python
def view_profile(user_id):
if not is_user_authenticated():
return redirect_to_login()
# 实际处理用户资料逻辑
print(f”显示用户 {user_id} 的资料”)
pass

def edit_settings(user_id):
if not is_user_authenticated():
return redirect_to_login()
# 实际处理设置编辑逻辑
print(f”编辑用户 {user_id} 的设置”)
pass

还有很多其他需要登录验证的函数…

“`

这种模式存在明显的问题:
1. 代码重复 (Repetition):你需要反复书写 if not is_user_authenticated(): return redirect_to_login() 这段逻辑。
2. 维护困难 (Maintainability):如果将来验证逻辑需要修改(比如增加权限检查),你需要修改所有相关的函数。
3. 代码臃肿 (Verbosity):核心业务逻辑被非核心的验证代码所干扰,降低了代码的可读性。

除了权限验证,类似的问题还出现在:
* 日志记录 (Logging):记录每个函数的调用、参数和返回值。
* 性能计时 (Timing):测量每个函数的执行时间。
* 缓存 (Caching):缓存函数的计算结果,避免重复计算。
* 事务管理 (Transaction Management):在函数执行前后处理数据库事务。

为了解决这些问题,我们可以将这些“附加”功能从核心业务逻辑中抽离出来,形成独立的模块。装饰器正是实现这一目标的一种优雅机制。

使用装饰器后,上面的例子可能会变成这样:

“`python
from my_decorators import login_required

@login_required
def view_profile(user_id):
# 实际处理用户资料逻辑
print(f”显示用户 {user_id} 的资料”)
pass # 假设登录验证成功,才执行到这里

@login_required
def edit_settings(user_id):
# 实际处理设置编辑逻辑
print(f”编辑用户 {user_id} 的设置”)
pass # 假设登录验证成功,才执行到这里

代码变得非常简洁和清晰!

“`

通过 @login_required 装饰器,我们将登录验证的逻辑与具体的视图函数解耦。视图函数只专注于自己的核心功能,而验证逻辑则由装饰器统一处理。这极大地提高了代码的可读性、可维护性和复用性。

2. 装饰器的基础:Python函数特性

要理解装饰器的工作原理,我们需要先回顾一下 Python 中函数的两个重要特性:

2.1 函数是“一等公民”(First-Class Objects)

在 Python 中,函数与其他数据类型(如整数、字符串、列表等)拥有同等的地位。这意味着:
* 函数可以被赋值给变量。
* 函数可以作为参数传递给另一个函数。
* 函数可以作为另一个函数的返回值。
* 函数可以存储在数据结构中(如列表、字典)。

示例:函数作为变量和参数

“`python

函数可以被赋值给变量

def say_hello(name):
return f”Hello, {name}!”

greet = say_hello # greet 现在引用了 say_hello 函数
print(greet(“Alice”)) # 输出: Hello, Alice!

函数可以作为参数传递

def call_function(func, args):
return func(
args)

print(call_function(say_hello, “Bob”)) # 输出: Hello, Bob!
“`

这个特性是装饰器能够接受函数作为输入并返回函数作为输出的基础。

2.2 闭包(Closures)

闭包是指一个函数(内部函数)记住了其创建时的环境,即使创建它的外层函数已经执行完毕并返回,这个内部函数仍然能够访问其外层函数作用域中的变量。

示例:一个简单的闭包

“`python
def outer_function(message):
# outer_function 的局部变量
# message 变量在 inner_function 的作用域外,但 inner_function 可以访问它

def inner_function():
    # inner_function 访问外层函数的 message 变量
    print(message)

# outer_function 返回 inner_function
return inner_function

创建一个闭包实例

my_closure 引用了 inner_function,并且记住了 message 的值 “Hello”

my_closure = outer_function(“Hello”)

外层函数 outer_function 已经执行完毕并返回了,

但 my_closure(即 inner_function)仍然能够访问 message 变量

my_closure() # 输出: Hello

再创建一个闭包实例,记住不同的 message

another_closure = outer_function(“World”)
another_closure() # 输出: World
“`

在装饰器中,外层函数(装饰器函数)会定义一个内层函数(通常称为 wrapperinner),这个内层函数就是返回的装饰后的新函数。闭包的特性使得 wrapper 函数能够记住并访问到外层函数传入的被装饰的原始函数以及其他相关变量,这是实现“包装”功能的核心。

3. 理解装饰器的工作原理

有了上述基础,我们就可以理解装饰器是如何工作的了。本质上,装饰器就是一个接收一个函数作为参数,并返回一个新的函数(通常是原函数的增强版本)的函数。

Python 的 @ 语法糖只是为了让这个过程更简洁。

考虑以下装饰器和函数:

“`python
def my_decorator(func):
print(“Inside my_decorator: Decorating function”, func.name)
def wrapper(args, kwargs):
print(“Inside wrapper: Before calling”, func.name)
result = func(
args, **kwargs) # 调用原始函数
print(“Inside wrapper: After calling”, func.name)
return result
print(“Inside my_decorator: Returning wrapper function”)
return wrapper

def greet(name):
print(f”Hello, {name}!”)
return f”Greeting result: Hello, {name}!”

使用 @ 语法糖应用装饰器

@my_decorator
def decorated_greet(name):
print(f”Hello, {name}!”)
return f”Greeting result: Hello, {name}!”
“`

当你定义 decorated_greet 并使用 @my_decorator 装饰它时,Python 实际上执行了以下操作:

  1. 定义原始函数 decorated_greet
  2. 调用 my_decorator(decorated_greet)
  3. my_decorator 的返回值(也就是 wrapper 函数)赋值给 decorated_greet 这个名字。

所以,行 @my_decorator def decorated_greet(name): ... 等价于:

“`python
def decorated_greet(name):
print(f”Hello, {name}!”)
return f”Greeting result: Hello, {name}!”

decorated_greet = my_decorator(decorated_greet)
“`

现在,当你在代码中调用 decorated_greet("Charlie") 时,你实际上是在调用 my_decorator 返回的那个 wrapper 函数。

wrapper 函数接收到参数 "Charlie"。在 wrapper 函数内部,它首先打印 “Inside wrapper: Before calling decorated_greet”,然后调用原始的 decorated_greet("Charlie") 函数。原始函数执行完毕后,wrapper 函数继续执行,打印 “Inside wrapper: After calling decorated_greet”,最后返回原始函数的结果。

整个流程是:

decorated_greet("Charlie")
-> 调用 wrapper("Charlie")
-> wrapper 执行 print("Inside wrapper: Before...")
-> wrapper 调用 func("Charlie") (这里的 func 实际上是原始的 decorated_greet 函数,因为闭包记住了它)
-> 原始 decorated_greet 执行 print("Hello, Charlie!") 并返回 "Greeting result: Hello, Charlie!"
-> wrapper 接收到返回值
-> wrapper 执行 print("Inside wrapper: After...")
-> wrapper 返回结果 "Greeting result: Hello, Charlie!"

这就是装饰器通过包装原始函数并插入额外逻辑来工作的基本原理。

4. 编写你的第一个实用装饰器:函数计时器

我们来编写一个实用的装饰器,用于测量任意函数的执行时间。

“`python
import time
import functools

def timer_decorator(func):
“””
一个装饰器,用于测量被装饰函数的执行时间
“””
@functools.wraps(func) # 使用 functools.wraps 保留原始函数的元数据
def wrapper_timer(args, kwargs):
“””
wrapper 函数,实现计时逻辑
“””
start_time = time.time() # 记录开始时间
value = func(
args, **kwargs) # 调用原始函数,并获取返回值
end_time = time.time() # 记录结束时间
run_time = end_time – start_time # 计算执行时间
print(f”函数 {func.name!r} 执行完毕,耗时: {run_time:.4f} 秒”)
return value # 返回原始函数的返回值
return wrapper_timer

应用装饰器

@timer_decorator
def slow_function(seconds):
“””
一个模拟耗时操作的函数
“””
print(f”开始执行 slow_function, 将暂停 {seconds} 秒…”)
time.sleep(seconds)
print(“slow_function 执行完毕”)
return f”暂停了 {seconds} 秒”

@timer_decorator
def fast_function(a, b):
“””
一个执行很快的函数
“””
print(f”开始执行 fast_function, 计算 {a} + {b}…”)
result = a + b
print(“fast_function 执行完毕”)
return result

调用被装饰的函数

slow_function(2)
print(“-” * 20)
result = fast_function(5, 3)
print(f”fast_function 的结果是: {result}”)

检查函数元数据 (在下一节解释 @functools.wraps 的重要性)

print(“\n检查函数元数据:”)
print(f”slow_function 的名字: {slow_function.name}”)
print(f”slow_function 的文档字符串: {slow_function.doc}”)
“`

代码解释:

  1. import timeimport functools:导入必要的模块。time 用于计时,functools 用于保留函数元数据。
  2. def timer_decorator(func)::定义装饰器函数 timer_decorator,它接收一个函数 func 作为参数。
  3. @functools.wraps(func):这是关键的一步,用于装饰内部的 wrapper_timer 函数。它的作用是将被装饰的原始函数 func 的元数据(如 __name__, __doc__, __module__, __dict__ 等)复制到 wrapper_timer 函数上。这样,当我们通过 slow_function.__name__slow_function.__doc__ 访问时,得到的是原始函数的属性,而不是 wrapper_timer 函数的属性,提高了代码的可调试性和内省能力。我们会在下一节详细讨论它。
  4. def wrapper_timer(*args, **kwargs)::定义内层函数 wrapper_timer。这个函数就是 timer_decorator 将要返回的新函数。它使用 *args**kwargs 来接受任意位置参数和关键字参数,并将它们传递给原始函数,这使得装饰器可以用于接受不同参数签名的函数。
  5. start_time = time.time():在调用原始函数前记录当前时间。
  6. value = func(*args, **kwargs):调用被装饰的原始函数 func,并传入从 wrapper_timer 接收到的参数。将原始函数的返回值保存在 value 变量中。
  7. end_time = time.time()run_time = end_time - start_time:在原始函数执行后记录结束时间并计算耗时。
  8. print(...):打印执行时间和函数名。使用 !r 格式化字符串可以打印出函数名的引号,使其更清晰。
  9. return value:返回原始函数的执行结果。这一点非常重要,否则被装饰的函数将无法返回其应有的值。
  10. return wrapper_timertimer_decorator 函数返回其内部定义的 wrapper_timer 函数。

通过 @timer_decorator 语法糖,slow_functionfast_function 在定义后立即被 timer_decorator 函数处理,它们的名称(slow_functionfast_function)现在指向了 timer_decorator 返回的 wrapper_timer 函数实例。当调用 slow_function(2)fast_function(5, 3) 时,实际执行的是对应的 wrapper_timer 实例,它负责计时并调用原始函数。

5. 装饰器的问题:函数元数据丢失与 functools.wraps

在上面的例子中,我们使用了 @functools.wraps(func)。这一步是极其重要的,如果没有它,你会发现被装饰函数的元数据(如 __name__, __doc__ 等)会变成内部 wrapper 函数的元数据。

我们来看一个没有使用 functools.wraps 的计时器装饰器版本:

“`python

没有使用 functools.wraps 的装饰器版本

def simple_timer_decorator(func):
def wrapper_timer(args, kwargs):
start_time = time.time()
value = func(
args, **kwargs)
end_time = time.time()
run_time = end_time – start_time
print(f”[Simple Timer] 函数 {func.name!r} 执行完毕,耗时: {run_time:.4f} 秒”)
return value
# simple_timer_decorator 返回 wrapper_timer
return wrapper_timer

@simple_timer_decorator
def another_slow_function(seconds):
“””
另一个模拟耗时操作的函数 (没有 wraps)
“””
time.sleep(seconds)
return f”暂停了 {seconds} 秒 (没有 wraps)”

调用函数

another_slow_function(1)

检查函数元数据

print(“\n检查函数元数据 (没有 wraps):”)
print(f”another_slow_function 的名字: {another_slow_function.name}”)
print(f”another_slow_function 的文档字符串: {another_slow_function.doc}”)
“`

运行上面的代码,你会发现输出的函数名和文档字符串不再是原始的 another_slow_function 的,而是 wrapper_timer 的。

“`
[Simple Timer] 函数 ‘another_slow_function’ 执行完毕,耗时: 1.xxxx 秒

检查函数元数据 (没有 wraps):
another_slow_function 的名字: wrapper_timer
another_slow_function 的文档字符串: wrapper_timer 函数,实现计时逻辑
“`

这可能导致一些依赖函数元数据的工具(如文档生成器、测试框架、调试器等)无法正确识别被装饰的函数,带来混淆和问题。

functools.wraps(func) 就是为了解决这个问题而生的。它是一个装饰器工厂(本身也是一个装饰器,但它用于装饰其他函数,并将原始函数的元数据复制到被装饰的函数上)。当你写 @functools.wraps(func) 装饰 wrapper 函数时,wraps 函数会将被装饰的原始函数 func 的相关属性(如 __name__, __doc__, __module__, __annotations__, __dict__ 等)复制到 wrapper 函数上。

因此,始终推荐在你的装饰器内部使用 @functools.wraps(func) 来装饰你的 wrapper 函数,以保持被装饰函数的元数据完整性。

6. 带参数的装饰器

有些时候,我们希望装饰器能够接受额外的配置参数。例如,一个权限验证装饰器可能需要知道具体是哪种权限,一个重试装饰器可能需要知道最大重试次数。

为了实现带参数的装饰器,我们需要在原有的两层结构(装饰器函数 -> wrapper 函数)外面再增加一层。最外层函数接收装饰器的参数,然后返回真正的装饰器函数。

结构变成这样:

“`python
def decorator_factory(arg1, arg2, …): # 最外层:接收装饰器参数
print(f”Inside decorator_factory, received args: {arg1}, {arg2}”)
def actual_decorator(func): # 第二层:真正的装饰器,接收被装饰函数
print(f”Inside actual_decorator, decorating: {func.name}”)
@functools.wraps(func)
def wrapper(args, kwargs): # 最内层:wrapper 函数,执行增强逻辑并调用原始函数
print(f”Inside wrapper, before calling {func.name}”)
# 在这里可以使用 arg1, arg2 等参数
result = func(
args, **kwargs)
print(f”Inside wrapper, after calling {func.name}”)
return result
print(“Inside actual_decorator, returning wrapper”)
return wrapper
print(“Inside decorator_factory, returning actual_decorator”)
return actual_decorator

使用带参数的装饰器

@decorator_factory(arg1_value, arg2_value)
def my_function(…):
pass
“`

当 Python 解释器看到 @decorator_factory(arg1_value, arg2_value) 这行时,它首先会调用 decorator_factory(arg1_value, arg2_value),这个调用会返回 actual_decorator 函数。然后,这个返回的 actual_decorator 函数会被用来装饰下面的 my_function,就像前面不带参数的装饰器一样。

示例:带参数的权限验证装饰器

假设我们有一个简单的权限系统,需要检查用户是否具备某个权限才能访问函数。

“`python
import functools

模拟用户权限检查函数

def check_permission(user, required_permission):
“””
模拟检查用户是否拥有指定权限
“””
print(f”Checking if user {user} has permission ‘{required_permission}’…”)
# 假设 user ‘admin’ 有 ‘admin’ 权限,其他用户有 ‘read’ 权限
if user == ‘admin’ and required_permission == ‘admin’:
print(“Permission granted.”)
return True
elif user != ‘admin’ and required_permission == ‘read’:
print(“Permission granted.”)
return True
else:
print(“Permission denied.”)
return False

def requires_permission(required_permission): # 最外层:接收装饰器参数 required_permission
“””
一个带参数的装饰器工厂,用于权限检查
“””
print(f”Decorator factory called for permission: {required_permission}”)

def actual_decorator(func): # 第二层:真正的装饰器,接收被装饰函数 func
    print(f"Actual decorator called for function: {func.__name__}")

    @functools.wraps(func)
    def wrapper_permission(*args, **kwargs): # 最内层:wrapper 函数
        # 在 wrapper 中,我们需要知道是哪个用户调用的,假设用户是第一个位置参数
        if not args:
            print("Error: User information is missing (expected as first argument).")
            return None # 或者抛出异常

        user = args[0] # 假设第一个参数是用户身份

        # 在 wrapper 内部使用外部传入的 required_permission 参数
        if check_permission(user, required_permission):
            print("Calling the original function...")
            # 如果权限通过,才调用原始函数
            return func(*args, **kwargs)
        else:
            print("Original function call blocked due to insufficient permissions.")
            # 如果权限不通过,不调用原始函数,返回 None 或特定的错误响应
            return None

    print("Actual decorator returning wrapper function.")
    return wrapper_permission

print("Decorator factory returning actual_decorator.")
return actual_decorator

使用带参数的装饰器

@requires_permission(‘admin’)
def admin_only_action(user, data):
“””
只有管理员能执行的操作
“””
print(f”{user} is performing admin action with data: {data}”)
return f”Admin action successful with data: {data}”

@requires_permission(‘read’)
def read_data_action(user, item_id):
“””
所有用户都能读取数据的操作
“””
print(f”{user} is reading item: {item_id}”)
return f”Read action successful for item: {item_id}”

print(“— Testing admin_only_action —“)

测试管理员访问

result_admin = admin_only_action(‘admin’, {‘config’: ‘sensitive’})
print(f”Result: {result_admin}\n”)

测试普通用户访问管理员操作

result_user_try_admin = admin_only_action(‘normal_user’, {‘config’: ‘sensitive’})
print(f”Result: {result_user_try_admin}\n”)

print(“— Testing read_data_action —“)

测试管理员访问读取操作

result_admin_read = read_data_action(‘admin’, 123)
print(f”Result: {result_admin_read}\n”)

测试普通用户访问读取操作

result_user_read = read_data_action(‘normal_user’, 456)
print(f”Result: {result_user_read}\n”)

检查元数据

print(“\n检查函数元数据 (带参数装饰器):”)
print(f”admin_only_action 的名字: {admin_only_action.name}”)
print(f”admin_only_action 的文档字符串: {admin_only_action.doc}”)
print(f”read_data_action 的名字: {read_data_action.name}”)
print(f”read_data_action 的文档字符串: {read_data_action.doc}”)
“`

代码解释:

  1. def requires_permission(required_permission)::最外层函数,接收装饰器的参数 required_permission
  2. 它返回 actual_decorator 函数。注意,actual_decorator 函数是定义在 requires_permission 内部的,因此通过闭包,actual_decorator 以及其内部的 wrapper_permission 能够访问到 required_permission 参数的值。
  3. def actual_decorator(func)::第二层函数,接收被装饰的原始函数 func。这就是我们之前见过的“标准的”装饰器结构。
  4. 它返回 wrapper_permission 函数。
  5. @functools.wraps(func):同样用于保留原始函数的元数据,放在 wrapper_permission 上。
  6. def wrapper_permission(*args, **kwargs)::最内层函数,接收调用被装饰函数时传入的参数。
  7. user = args[0]:这里我们做了一个简化假设,认为调用函数时的第一个位置参数就是用户身份。在实际应用中,如何获取用户身份取决于你的框架或设计(可能从请求对象、线程局部存储或其他地方获取)。
  8. if check_permission(user, required_permission)::在 wrapper 内部,使用闭包记住的 required_permission 参数和从函数调用中获取的 user 参数来执行权限检查。
  9. 根据检查结果,决定是否调用原始函数 func(*args, **kwargs)

通过这种三层结构,我们成功创建了一个可以配置权限要求的装饰器。

7. 装饰类中的方法

装饰器不仅可以装饰独立的函数,也可以装饰类中的方法(包括实例方法、类方法和静态方法)。工作原理是类似的,因为方法在 Python 中也是函数对象。唯一的区别在于,当你装饰一个方法时,被装饰的方法作为函数传递给装饰器时,它的第一个参数会是被绑定或未绑定的实例(对于实例方法),或者类(对于类方法)。

  • 装饰实例方法wrapper 函数的第一个参数会是 self
  • 装饰类方法 (@classmethod 修饰的方法):wrapper 函数的第一个参数会是 cls (类本身)。
  • 装饰静态方法 (@staticmethod 修饰的方法):wrapper 函数不会接收额外的第一个参数(selfcls)。

编写一个可以装饰任何类型方法的通用装饰器通常需要 *args**kwargs 来灵活处理参数。

示例:装饰类方法

我们使用之前的 timer_decorator 来装饰一个类的方法。

“`python
import time
import functools

复用之前的 timer_decorator

def timer_decorator(func):
@functools.wraps(func)
def wrapper_timer(args, kwargs):
start_time = time.time()
value = func(
args, **kwargs)
end_time = time.time()
run_time = end_time – start_time
# 对于方法,args[0] 通常是 self 或 cls
instance_or_class = args[0].class.name if args else “Unknown”
print(f”类/实例 {instance_or_class} 中的方法 {func.name!r} 执行完毕,耗时: {run_time:.4f} 秒”)
return value
return wrapper_timer

class MyClass:
def init(self, name):
self.name = name

@timer_decorator # 装饰实例方法
def process_instance_data(self, data):
    """
    处理实例数据的方法
    """
    print(f"{self.name} 正在处理数据: {data}")
    time.sleep(0.5)
    return f"Processed by {self.name}"

@classmethod # @classmethod 应该在装饰器下面
@timer_decorator # 装饰类方法
def create_instance(cls, name):
    """
    创建类实例的方法
    """
    print(f"使用类方法 {cls.__name__}.create_instance 创建实例...")
    time.sleep(0.3)
    return cls(name)

@staticmethod # @staticmethod 应该在装饰器下面
@timer_decorator # 装饰静态方法
def helper_method(x, y):
    """
    一个静态辅助方法
    """
    print(f"使用静态方法计算 {x} * {y}...")
    time.sleep(0.1)
    return x * y

调用被装饰的方法

instance1 = MyClass(“Instance1”)
instance1.process_instance_data([1, 2, 3])
print(“-” * 20)

instance2 = MyClass.create_instance(“Instance2″)
print(f”创建了实例: {instance2.name}”)
print(“-” * 20)

result_static = MyClass.helper_method(7, 8)
print(f”静态方法结果: {result_static}”)

检查元数据

print(“\n检查方法元数据:”)
print(f”process_instance_data 的名字: {MyClass.process_instance_data.name}”)
print(f”create_instance 的名字: {MyClass.create_instance.name}”)
print(f”helper_method 的名字: {MyClass.helper_method.name}”)
“`

注意顺序: 当同时使用 @classmethod@staticmethod 和自定义装饰器时,自定义装饰器应该放在 @classmethod@staticmethod上面。这是因为 @classmethod@staticmethod 会修改方法的调用方式,它们需要先被应用到原始函数上,然后自定义装饰器再包装这个被 @classmethod@staticmethod 处理过的“函数”。

例如, @classmethod 的作用是将被装饰的方法绑定到类而不是实例上,并在调用时将类作为第一个参数(cls)传递。如果 @timer_decorator@classmethod 下面,timer_decorator 将接收到原始的方法函数;如果 @timer_decorator@classmethod 上面,timer_decorator 将接收到已经被 @classmethod 转换过的、绑定到类上的方法对象。通常我们希望装饰器作用于原始的函数定义上,或者至少是经过 @classmethod/@staticmethod 处理后的函数对象,所以 @classmethod/@staticmethod 应该在底层先处理。

8. 装饰器链(Chaining Decorators)

你可以在同一个函数或方法上应用多个装饰器。只需要简单地将它们叠放在被装饰对象的定义上方。

python
@decorator1
@decorator2
@decorator3
def my_function():
pass

执行顺序是从下往上:
1. 首先,my_functiondecorator3 装饰:my_function = decorator3(my_function)
2. 然后,上一步的结果(即 decorator3 返回的新函数)再被 decorator2 装饰:my_function = decorator2(my_function)
3. 最后,上一步的结果再被 decorator1 装饰:my_function = decorator1(my_function)

所以,当调用 my_function() 时,执行流将从最外层的装饰器(decorator1 的 wrapper)开始,层层深入,直到最里层的 wrapper 调用原始的 my_function

示例:结合计时器和权限装饰器

“`python
import time
import functools

复用之前的 timer_decorator 和 requires_permission

注意:为了链式调用,我们的装饰器需要能够处理返回值和参数

timer_decorator 和 requires_permission 都已经可以正确处理参数和返回值

def timer_decorator(func):
@functools.wraps(func)
def wrapper_timer(args, kwargs):
start_time = time.time()
value = func(
args, **kwargs) # 调用下一层 (可能是另一个 wrapper 或原始函数)
end_time = time.time()
run_time = end_time – start_time
print(f”[TIMER] 函数 {func.name!r} 耗时: {run_time:.4f} 秒”)
return value
return wrapper_timer

def requires_permission(required_permission):
def actual_decorator(func):
@functools.wraps(func)
def wrapper_permission(args, *kwargs):
if not args:
print(“[PERMISSION] Error: User information missing.”)
return None

        user = args[0]
        if check_permission(user, required_permission):
            print(f"[PERMISSION] User {user} has permission '{required_permission}'. Proceeding.")
            # 如果权限通过,调用下一层 (可能是另一个 wrapper 或原始函数)
            return func(*args, **kwargs)
        else:
            print(f"[PERMISSION] User {user} lacks permission '{required_permission}'. Blocked.")
            return None # 权限不通过,阻止向下执行

    return wrapper_permission
return actual_decorator

模拟用户权限检查函数 (与之前相同)

def check_permission(user, required_permission):
print(f”[PERMISSION] Checking permission for user {user}…”)
if user == ‘admin’ and required_permission == ‘admin’:
return True
elif user != ‘admin’ and required_permission == ‘read’:
return True
else:
return False

应用装饰器链

执行顺序:从下往上应用,从上往下调用

实际调用时:timer_decorator -> requires_permission -> original_function

@timer_decorator # 最外层调用 (wrapper_timer)
@requires_permission(‘admin’) # 中间层调用 (wrapper_permission)
def sensitive_admin_action(user, config_data):
“””
一个需要管理员权限并需要计时的敏感操作
“””
print(f”[{user}] 执行敏感操作,处理数据: {config_data}”)
time.sleep(0.8)
print(f”[{user}] 敏感操作执行完毕”)
return “Sensitive action completed”

print(“— Testing sensitive_admin_action with admin —“)
sensitive_admin_action(‘admin’, {‘setting’: ‘important’})
print(“-” * 20)

print(“— Testing sensitive_admin_action with normal_user —“)
sensitive_admin_action(‘normal_user’, {‘setting’: ‘important’})
print(“-” * 20)

检查元数据

print(“\n检查函数元数据 (装饰器链):”)
print(f”sensitive_admin_action 的名字: {sensitive_admin_action.name}”)
print(f”sensitive_admin_action 的文档字符串: {sensitive_admin_action.doc}”)
“`

执行流程分析:

当你调用 sensitive_admin_action('admin', ...) 时:

  1. 你调用的是 timer_decorator 返回的 wrapper_timer 函数。
  2. wrapper_timer 记录开始时间,然后调用它包装的函数,这个函数是 requires_permission('admin') 返回的 wrapper_permission 函数。
  3. wrapper_permission 接收到参数 ('admin', {...}),检查用户 'admin' 是否有 'admin' 权限。
  4. check_permission 返回 True
  5. wrapper_permission 打印权限通过信息,然后调用它包装的函数,这个函数是原始的 sensitive_admin_action 函数。
  6. 原始的 sensitive_admin_action 执行打印和 time.sleep(0.8)
  7. 原始函数返回 "Sensitive action completed"
  8. wrapper_permission 接收到返回值,并返回给上一层(wrapper_timer)。
  9. wrapper_timer 接收到返回值,记录结束时间,计算并打印耗时信息,然后返回最终结果。

当你调用 sensitive_admin_action('normal_user', ...) 时:

  1. 你调用的是 timer_decorator 返回的 wrapper_timer 函数。
  2. wrapper_timer 记录开始时间,然后调用它包装的 wrapper_permission 函数。
  3. wrapper_permission 接收到参数 ('normal_user', {...}),检查用户 'normal_user' 是否有 'admin' 权限。
  4. check_permission 返回 False
  5. wrapper_permission 打印权限不足信息,调用原始函数,直接返回 None
  6. wrapper_timer 接收到 None 作为返回值,记录结束时间,计算并打印耗时信息(即使原始函数未执行),然后返回 None

理解装饰器链的应用顺序(从下往上)和调用顺序(从上往下)是正确使用多装饰器的关键。

9. 装饰器在实际开发中的应用

装饰器是 Python 框架和库中无处不在的特性。熟悉它们的应用场景能帮助你更好地理解和使用这些工具,甚至创建自己的高级抽象。常见的应用场景包括:

  • Web 框架路由:Flask/Django 等框架使用 @app.route('/')@path('...') 来将 URL 映射到视图函数。
    “`python
    from flask import Flask
    app = Flask(name)

    @app.route(‘/’) # 这是一个装饰器
    def index():
    return ‘Hello, World!’
    * **权限/认证检查**:如前所示,检查用户是否登录、是否拥有特定权限才能访问某些功能。
    * **日志记录和监控**:自动记录函数调用、参数、返回值和异常,或者记录执行时间用于性能分析。
    * **缓存**:使用 `@functools.lru_cache` 可以轻松地缓存函数的计算结果,提高性能。
    python
    import functools

    @functools.lru_cache(maxsize=128) # 缓存最近 128 次调用结果
    def fibonacci(n):
    if n <= 1:
    return n
    return fibonacci(n – 1) + fibonacci(n – 2)
    “`
    * 重试机制:当函数因为临时性错误(如网络问题)失败时,自动重试几次。
    * 事务管理:在数据库操作函数上应用装饰器,自动处理事务的提交或回滚。
    * 输入验证:检查函数参数是否符合预期格式或类型。
    * 资源管理:确保在使用完文件、网络连接等资源后正确关闭。

10. 总结与最佳实践

Python 装饰器是构建清晰、可维护和可重用代码的强大工具。它们通过将额外的逻辑(如日志、计时、权限检查)与核心业务逻辑分离,提高了代码的模块化程度。

核心要点回顾:

  • 基础:理解函数是“一等公民”和闭包是理解装饰器工作原理的关键。
  • 工作原理@decorator 语法糖等价于 function = decorator(function)。装饰器是一个函数,它接受一个函数作为输入,并返回一个增强后的新函数(通常是一个内部定义的 wrapper 函数)。
  • Wrapper 函数wrapper 函数是装饰器的核心,它负责在调用原始函数之前或之后执行额外的逻辑,并通常接收 *args**kwargs 来兼容任意参数签名的函数。
  • functools.wraps强烈推荐wrapper 函数上使用 @functools.wraps(func) 来保留原始函数的元数据,避免潜在的问题。
  • 带参数的装饰器:通过增加一层函数(装饰器工厂),使得装饰器能够接收额外的配置参数。
  • 装饰方法:装饰器同样适用于类的方法,使用 *args**kwargs 可以处理 selfcls 参数。与 @classmethod/@staticmethod 结合使用时,自定义装饰器通常放在它们上方。
  • 装饰器链:可以在同一函数上应用多个装饰器,它们从下往上应用,从上往下调用。

最佳实践:

  • 保持装饰器简洁:一个装饰器应该只做一件事情,如果需要多个功能,使用装饰器链。
  • 使用 functools.wraps:这几乎是一条硬性规定,除非你有特殊理由不需要保留元数据。
  • 妥善处理参数和返回值:确保你的 wrapper 函数能够正确接收和传递任意位置和关键字参数(使用 *args**kwargs),并正确返回原始函数的计算结果。
  • 异常处理:考虑在 wrapper 中如何处理被装饰函数可能抛出的异常。你可能需要在 try...except 块中调用原始函数。
  • 文档和命名:给你的装饰器和 wrapper 函数起一个有意义的名字,并编写文档字符串说明它的用途、参数和行为。functools.wraps 会帮助复制原始函数的文档,但描述装饰器本身的文档也很重要。
  • 避免过度使用:虽然装饰器很强大,但过度或不恰当的使用可能会降低代码的可读性。确保使用装饰器确实能够简化代码,而不是使其更复杂。

掌握 Python 装饰器,就像掌握了代码简化的一把魔法钥匙。它能帮助你写出更优雅、更强大、更易于维护的 Python 代码。现在,就开始在你的项目中实践它们吧!


发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部