python 装饰器


Python 中的装饰器(Decorators) 是一种强大且优雅的语法糖,它允许你在不修改原始函数或类定义的情况下,动态地给它们添加功能或修改它们的行为。你可以将装饰器想象成一个“包装器”,它在不改变被包装对象内容的前提下,为其增加新的特性。


什么是装饰器?

从本质上讲,装饰器是一个接受函数(或类)作为输入,并返回一个新函数(或新类)的可调用对象(callable)。这个新函数/类通常会包含原始函数/类的逻辑,并在此基础上添加一些额外的功能。

Python 中使用 @ 符号来应用装饰器,它放在函数或类定义的上方。

# 定义一个装饰器函数
def my_decorator(func):
    def wrapper():
        print("Something is happening before the function is called.")
        func() # 调用原始函数
        print("Something is happening after the function is called.")
    return wrapper

@my_decorator # 使用 @ 语法糖应用装饰器
def say_hello():
    print("Hello!")

say_hello()

输出:

Something is happening before the function is called.
Hello!
Something is happening after the function is called.

在这个例子中,my_decorator 就是一个装饰器。@my_decorator 放在 say_hello 函数上方,等同于执行了 say_hello = my_decorator(say_hello)

装饰器的工作原理

  1. 定义装饰器:你首先定义一个函数(或类,更高级的用法),它接受一个函数作为参数。
  2. 定义内部函数(Wrapper):在这个装饰器内部,你通常会定义一个嵌套函数(通常命名为 wrapper),这个 wrapper 函数会包含你想要添加的额外逻辑,并且会在某个地方调用原始函数。
  3. 返回 Wrapper:装饰器函数最后返回这个 wrapper 函数。
  4. 应用装饰器:当你使用 @decorator_name 语法时,Python 会自动将被装饰的函数作为参数传递给 decorator_name 函数,然后用返回的 wrapper 函数替换掉原始函数。

为什么使用装饰器?

装饰器提供了一种优雅的方式来实现以下目标:

  1. 代码重用:将通用的功能(如日志、性能计时、权限检查、缓存等)封装成装饰器,可以在多个函数或类中重复使用,避免代码重复。
  2. 关注点分离:将核心业务逻辑与非核心的横切关注点(cross-cutting concerns)分离。例如,一个函数只负责其核心计算,而日志记录则由装饰器处理。
  3. 提高可读性:通过简洁的 @ 语法,代码的意图更加清晰。
  4. 不修改原始代码:你可以在不触碰原始函数或类定义的情况下,扩展或修改其行为。

装饰器的常见应用场景

  • 日志记录(Logging):记录函数调用的时间、参数和返回值。
  • 性能分析(Profiling):测量函数的执行时间。
  • 权限检查(Authorization):验证用户是否有权访问某个函数或资源。
  • 缓存(Caching):缓存函数的返回结果,避免重复计算。
  • 输入验证(Input Validation):在函数执行前检查输入参数的有效性。
  • 重试机制(Retries):当函数失败时自动重试。
  • API 路由(API Routing):在 Web 框架中,如 Flask 和 Django,装饰器常用于将 URL 路径映射到视图函数。

带参数的函数装饰器

如果被装饰的函数需要参数,你的 wrapper 函数也必须能接收这些参数,并传递给原始函数。这通常通过 *args**kwargs 来实现。

def log_arguments(func):
    def wrapper(*args, **kwargs): # 接收任意位置参数和关键字参数
        print(f"Calling {func.__name__} with args: {args}, kwargs: {kwargs}")
        result = func(*args, **kwargs)
        print(f"{func.__name__} returned: {result}")
        return result
    return wrapper

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

@log_arguments
def greet(name, greeting="Hello"):
    return f"{greeting}, {name}!"

add(10, 20)
greet("Alice", greeting="Hi")

带参数的装饰器

有些时候,你可能需要自定义装饰器的行为,这就需要装饰器本身接收参数。这通常通过一个三层嵌套的结构来实现。外层函数接收装饰器参数,然后返回真正的装饰器函数。

def repeat(num_times): # 第一层:接收装饰器参数
    def decorator(func): # 第二层:接收被装饰的函数
        def wrapper(*args, **kwargs): # 第三层:接收被装饰函数的参数
            for _ in range(num_times):
                func(*args, **kwargs)
        return wrapper
    return decorator

@repeat(num_times=3) # 调用装饰器,传入参数
def bark():
    print("Woof!")

@repeat(num_times=2)
def greet_person(name):
    print(f"Hello, {name}!")

bark()
print("---")
greet_person("Bob")

类装饰器

装饰器不仅可以装饰函数,还可以装饰类。此外,你也可以使用类来作为装饰器,如果这个类实现了 __call__ 方法。

# 类作为装饰器
class CountCalls:
    def __init__(self, func):
        self.func = func
        self.num_calls = 0

    def __call__(self, *args, **kwargs): # 使类的实例可调用
        self.num_calls += 1
        print(f"Call {self.num_calls} of {self.func.__name__}")
        return self.func(*args, **kwargs)

@CountCalls
def say_hi():
    print("Hi!")

say_hi()
say_hi()

@functools.wraps

在使用装饰器时,一个常见的问题是,装饰器会替换掉原始函数的元数据(如 __name____doc__)。为了解决这个问题,Python 提供了 functools.wraps 装饰器。

import functools

def my_logging_decorator(func):
    @functools.wraps(func) # 使用 wraps 装饰器
    def wrapper(*args, **kwargs):
        """This is the wrapper function's docstring."""
        print(f"Logging call to {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@my_logging_decorator
def my_function():
    """This is my_function's docstring."""
    pass

print(my_function.__name__) # 输出: my_function
print(my_function.__doc__)  # 输出: This is my_function's docstring.

如果没有 @functools.wraps(func)my_function.__name__ 会是 wrappermy_function.__doc__ 会是 wrapper 函数的文档字符串。


装饰器是 Python 中一个非常强大且常用的高级特性,理解它对于编写简洁、可维护和可扩展的代码至关重要。

你对装饰器还有什么具体的问题吗?

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容