Python-装饰器(Decorator)详解

在python中,函数是一等公民,意味着函数可以像其他对象一样被赋值、传递参数、作为返回值等。装饰器的基本语法是使用@符号将一个函数作为参数传递给另一个函数(即装饰器)。被装饰的函数在被调用时,实际上会执行装饰器函数返回的新函数‌。简单来说,装饰器就是一个返回函数的函数。使用简洁,直接在函数定义上方加上 @decorator 语法糖。

1装饰器的基本概念

装饰器是为了解耦代码的逻辑、提高代码的复用性,并且让函数和类的行为在不修改其原始代码的前提下得以扩展。能有效地将某些附加行为抽象出来,使得主业务逻辑(例如函数本身)保持简洁和专注。装饰器本质上是一个接受函数作为参数并返回一个新函数的函数。它常用于:

  • 在函数执行前后添加额外的行为。
  • 修改函数的输入或输出。
  • 为函数增加一些元数据。
  • 装饰器的应用场景非常广泛,例如:日志记录、权限检查、性能计时、缓存等。装饰器的引入可以解决以下几个问题:

    代码复用:装饰器可以将某些跨多个函数或类的功能(如日志、权限校验、缓存等)提取出来,以便于复用,避免重复代码。

    增强代码的可维护性:装饰器使得附加功能与主功能分离,从而使代码更清晰,容易理解与维护。通过装饰器,修改或增加功能时无需修改原有函数或类的实现。

    关注点分离(Separation of Concerns):装饰器让你可以将不同的功能分开处理,将与业务逻辑无关的操作(如日志记录、性能监控等)从核心业务代码中剥离出来。

    灵活性和扩展性:使用装饰器可以灵活地动态修改函数或方法的行为,而无需修改函数本身的代码。

    2装饰器的使用

    2.1 基本结构

    最简单的装饰器形式如下:

    def decorator(func):
        def wrapper():
    
            print("Before function call")
    
            func()  # 调用原函数
    
            print("After function call")
    
        return wrapper
    
    # 使用装饰器
    @decorator
    def say_hello():
        print("Hello!")
    
    # 调用装饰后的函数
    say_hello()

    解释:

  • decorator 是一个装饰器函数,它接收一个函数 func 作为参数。
  • wrapper 是一个新的函数,它在调用原函数 func() 之前和之后添加了自定义的行为。
  • @decorator 语法实际上是 say_hello = decorator(say_hello),即用装饰器修改 say_hello 函数。
  • 输出:

    Before function call

    Hello!

    After function call

    2.2 带参数的装饰器

    装饰器可以接受参数。为了让装饰器支持带参数的函数,我们需要在 wrapper 函数中使用 *args 和 **kwargs 来传递参数。

    示例:

    def decorator(func):
        def wrapper(*args, **kwargs):
    
            print("Before function call")
    
            result = func(*args, **kwargs)
    
            print("After function call")
    
            return result
    
        return wrapper
    
    @decorator
    def add(a, b):
    
        return a + b
    
    result = add(3, 5)
    
    print("Result:", result)

    解释:

  • wrapper 使用 *args 和 **kwargs 传递给被装饰函数,这样它就可以处理带有任意参数的函数。
  • 在 wrapper 中,原函数 func(*args, **kwargs) 被调用,并且它的结果被返回。
  • 输出:

    Before function call

    After function callResult: 8

    2.3 装饰器的嵌套

    装饰器可以嵌套使用,即一个函数可以同时被多个装饰器修饰。

    示例:多个装饰器

    def decorator1(func):
        def wrapper(*args, **kwargs):
    
            print("Decorator 1 - Before function call")
    
            result = func(*args, **kwargs)
    
            print("Decorator 1 - After function call")
    
            return result
    
        return wrapper
    
    def decorator2(func):
        def wrapper(*args, **kwargs):
    
            print("Decorator 2 - Before function call")
    
            result = func(*args, **kwargs)
    
            print("Decorator 2 - After function call")
    
            return result
    
        return wrapper
    
    @decorator1
    @decorator2
    def say_hello():
    
        print("Hello!")
    
    say_hello()

    解释:

  • 装饰器的顺序是从下到上的,即 @decorator2 首先应用,然后是 @decorator1。
  • 调用 say_hello() 时,会先执行 decorator2,然后是 decorator1。
  • 输出:

    Decorator 2 – Before function call

    Decorator 1 – Before function call

    Hello!

    Decorator 1 – After function call

    Decorator 2 – After function call

    2.4 使用 functools.wraps 保持原函数的元数据

    当我们使用装饰器时,装饰器内部的 wrapper 函数会替代原始函数。这意味着原函数的名称、文档字符串(docstring)等信息会丢失。为了避免这种情况,可以使用 functools.wraps 来保持原函数的元数据。

    示例:使用 functools.wraps

    import functools
    
    def decorator(func):    
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
    
            print("Before function call")
    
            result = func(*args, **kwargs)
    
            print("After function call")
    
            return result
    
        return wrapper
    
    @decorator
    def say_hello():
        """This is the say_hello function."""
    
        print("Hello!")
    
    print(say_hello.__name__)  # 输出:say_hello
    
    print(say_hello.__doc__)  # 输出:This is the say_hello function.

    解释:

  • @functools.wraps(func) 保证了装饰器不改变原函数的名称、文档字符串等元数据。
  • 如果没有使用 wraps,则 say_hello.__name__ 会返回 wrapper,而不是 say_hello。
  • 2.5 带参数的装饰器

    有时我们可能希望为装饰器传递参数(例如动态调整装饰器的行为)。为了实现这一点,我们需要使用三层嵌套函数

    示例:

    def decorator_with_args(arg):
    
        def decorator(func):
    
            def wrapper(*args, **kwargs):
    
                print(f"Decorator argument: {arg}")
    
                return func(*args, **kwargs)
    
            return wrapper
    
        return decorator
    
    @decorator_with_args("Hello")
    def greet(name):
    
        print(f"Greetings, {name}!")
    
    greet("Alice")
    

    解释:

  • decorator_with_args 是一个接受参数的装饰器,它返回一个装饰器函数 decorator。
  • decorator 是实际的装饰器,它接受一个函数 func,并在 wrapper 函数中添加新的功能。
  • @decorator_with_args("Hello") 等价于 greet = decorator_with_args("Hello")(greet)。
  • 输出:

    Decorator argument: Hello

    Greetings, Alice!

    2.6 类方法和静态方法的装饰器

    在类中使用装饰器时,需要注意装饰器的作用对象(类方法、实例方法、静态方法)。对于实例方法,装饰器会多一个 self 参数,对于类方法会有 cls 参数。

    示例:装饰器作用于实例方法

    class MyClass:
        def decorator(func):
            def wrapper(self, *args, **kwargs):
    
                print("Before calling instance method")
    
                result = func(self, *args, **kwargs)
    
                print("After calling instance method")
    
                return result
    
            return wrapper
    
        @decorator
        def greet(self, name):
    
            print(f"Hello, {name}!")
    
    obj = MyClass()
    
    obj.greet("Alice")

    示例:装饰器作用于类方法和静态方法

    class MyClass:    
        @staticmethod
        def decorator(func):
    
            def wrapper(*args, **kwargs):
    
                print("Before calling static method")
    
                result = func(*args, **kwargs)
    
                print("After calling static method")
    
                return result
    
            return wrapper
    
        @decorator    
        @staticmethod
        def greet(name):
    
            print(f"Hello, {name}!")
    
    MyClass.greet("Alice")

    3、python内置装饰器

    常见的有:

    1. @staticmethod – 用于定义静态方法。
    2. @classmethod – 用于定义类方法。
    3. @property – 将方法转化为属性(getter)。
    4. @functools.lru_cache – 用于缓存函数结果,提升性能。
    5. @functools.wraps – 保留被装饰函数的元数据。
    6. @abstractmethod – 定义抽象方法。
    7. @property.setter – 为属性添加 setter 方法。
    8. @property.deleter – 为属性添加 deleter 方法。

    3.1 @staticmethod

    @staticmethod 装饰器用于将一个方法定义为静态方法。静态方法不需要访问实例 (self) 或类 (cls),它通常用于一些与类本身相关但不需要访问实例属性或方法的功能。

    示例:

    class MyClass:    
    
        @staticmethod
        def greet(name):
    
            print(f"Hello, {name}!")
    
    # 静态方法不需要实例化类
    MyClass.greet("Alice")

    输出:

    Hello, Alice!

    3.2 @classmethod

    @classmethod 装饰器用于将方法定义为类方法。类方法接收类本身作为第一个参数(通常命名为 cls),而不是实例。它通常用于操作类的属性或提供一些与类本身相关的功能。

    示例:

    class MyClass:
    
        count = 0
    
        @classmethod
        def increment_count(cls):
    
            cls.count += 1
    
            print(f"Count: {cls.count}")
    
    MyClass.increment_count()  # 调用类方法

    输出:

    Count: 1

    3.3 @property

    @property 装饰器用于将方法变为属性,这意味着该方法的调用方式就像访问属性一样,而不需要显式调用方法。常用于定义只读属性,或者为属性添加 getter 和 setter 方法。

    示例:

    class Circle:
    
        def __init__(self, radius):
    
            self._radius = radius
    
        @property
        def radius(self):
    
            return self._radius
    
        @property
        def area(self):
    
            return 3.14 * self._radius ** 2
    
    c = Circle(5)
    print(c.radius)  # 直接访问属性
    print(c.area)  # 直接访问属性

    输出:

    5

    78.5

    3.4 @functools.lru_cache

    @lru_cache 是 functools 模块提供的一个装饰器,用于对函数的结果进行缓存。对于相同的参数,lru_cache 会返回缓存中的结果,从而避免重复计算,提升函数的效率。LRU 代表 "Least Recently Used"(最少使用)。

    示例:

    import functools
    
    @functools.lru_cache(maxsize=None)
    def fibonacci(n):
    
        if n < 2:
    
            return n
    
        return fibonacci(n - 1) + fibonacci(n - 2)
    
    print(fibonacci(35))
    
    #@lru_cache 将自动缓存 fibonacci 函数的结果,避免重复计算,提高性能。

    3.5 @functools.wraps

    @wraps 是 functools 模块中的一个装饰器,用于确保被装饰函数的元数据(如函数名、文档字符串等)在装饰后保持不变。当我们编写装饰器时,通常会使用 @wraps 来避免装饰器修改原函数的名称和文档字符串。

    示例:

    import functools
    
    def decorator(func):    
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
    
            print("Before function call")
    
            return func(*args, **kwargs)
    
        return wrapper
    
    @decorator
    def greet(name):
        """This is the greet function"""
    
        print(f"Hello, {name}!")
    
    print(greet.__name__)  # 输出:greet
    print(greet.__doc__)   # 输出:This is the greet function

    输出:

    Before function call

    Hello, Alice!

    @functools.wraps 保证了 greet 函数的名称和文档字符串没有被 wrapper 函数覆盖。

    3.6 @abstractmethod

    @abstractmethod 是 abc 模块提供的装饰器,用于声明一个方法为抽象方法。抽象方法不能直接在基类中实现,而必须在子类中实现。它通常用于定义抽象基类(ABC,Abstract Base Classes)。

    示例:

    from abc import ABC, abstractmethod
    
    class Shape(ABC):    
        @abstractmethod
        def area(self):
            pass
    
    class Circle(Shape):
    
        def __init__(self, radius):
    
            self.radius = radius
    
        def area(self):
    
            return 3.14 * self.radius ** 2
    
    # 创建实例时,必须实现抽象方法
    circle = Circle(5)
    print(circle.area())

    输出:

    78.5

    3.7 @property.setter

    @property.setter 装饰器用于给 @property 装饰器定义的属性添加 setter 方法。通过 setter 方法,可以控制属性值的设置。

    示例:

    class Circle:
    
        def __init__(self, radius):
    
            self._radius = radius
    
        @property
        def radius(self):
    
            return self._radius
    
        @radius.setter
        def radius(self, value):
    
            if value <= 0:
    
                raise ValueError("Radius must be positive.")
    
            self._radius = value
    
    circle = Circle(5)
    print(circle.radius)  # 访问属性
    
    circle.radius = 10  # 设置属性
    print(circle.radius)  # 访问更新后的属性

    输出:

    5

    10

    3.8 @property.deleter

    @property.deleter 装饰器用于定义删除属性的方法,允许通过 del 删除对象的属性。

    示例:

    class Circle:
        def __init__(self, radius):
    
            self._radius = radius
    
        @property
        def radius(self):
    
            return self._radius
    
        @radius.deleter
        def radius(self):
    
            print("Deleting radius")
    
            del self._radius
    
    circle = Circle(5)
    print(circle.radius)  # 访问属性
    del circle.radius     # 删除属性

    输出:

    5

    Deleting radius

    3.9 @staticmethod 与 @classmethod 的组合使用

    有时我们会将 @staticmethod 和 @classmethod 与其他装饰器结合使用。例如,可以在类方法中使用 @classmethod 与 @staticmethod 组合,在方法中执行类和实例的操作。

    4、 装饰器的实际作用示例

    以下是一些装饰器常见的应用场景以及它们如何影响代码行为。

    4.1 日志记录

    假设你需要为多个函数添加日志记录功能,可以通过装饰器来避免重复编写日志记录代码。

    def log_decorator(func):
    
        def wrapper(*args, **kwargs):
    
            print(f"Calling function {func.__name__} with arguments {args} and keyword arguments {kwargs}")
    
            result = func(*args, **kwargs)
    
            print(f"Function {func.__name__} returned {result}")
    
            return result
    
        return wrapper
    
    @log_decorator
    def add(a, b):
    
        return a + b
    
    @log_decorator
    def multiply(a, b):
    
        return a * b
    
    
    add(1, 2)
    
    multiply(3, 4)

    输出:

    Calling function add with arguments (1, 2) and keyword arguments {}

    Function add returned 3

    Calling function multiply with arguments (3, 4) and keyword arguments {}

    Function multiply returned 12

    装饰器使得日志记录功能从业务逻辑中分离开来,你只需要为函数加上 @log_decorator 装饰器,而不需要在每个函数中重复编写日志相关代码。

    4.2 性能监控

    如果你希望为多个函数监控执行时间,可以通过装饰器轻松实现:

    import time
    
    def time_decorator(func):
        def wrapper(*args, **kwargs):
    
            start_time = time.time()
    
            result = func(*args, **kwargs)
    
            end_time = time.time()
    
            print(f"Function {func.__name__} took {end_time - start_time} seconds to execute")
    
            return result
    
        return wrapper
    
    @time_decorator
    def slow_function():
    
        time.sleep(2)
    
    @time_decorator
    def fast_function():
    
        time.sleep(0.5)
    
    slow_function()
    fast_function()

    输出:

    Function slow_function took 2.002345085144043 seconds to execute

    Function fast_function took 0.5001745223999023 seconds to execute

    装饰器自动记录了函数的执行时间,避免了在每个函数内部手动写 time.time() 计算执行时间的代码。

    4.3 权限验证

    在 web 开发中,常常需要在处理请求之前验证用户的权限。这时可以使用装饰器来为多个视图函数或 API 接口加上权限检查。

    def permission_required(func):
        def wrapper(*args, **kwargs):
    
            if not has_permission():
    
                raise PermissionError("You do not have permission to access this resource.")
    
            return func(*args, **kwargs)
    
        return wrapper
    
    def has_permission():
    
        # 假设检查某个用户是否有权限
        return False
    
    @permission_required
    def sensitive_data():
    
        return "This is sensitive data."
    
    try:
    
        sensitive_data()
    
    except PermissionError as e:
    
        print(e)

    输出:

    You do not have permission to access this resource.

    装饰器为 sensitive_data 函数增加了权限检查逻辑。这样,后续所有需要权限验证的函数都可以通过添加 @permission_required 来使用该功能。

    4.4 缓存

    常见的缓存装饰器可以缓存函数的返回值,避免重复计算:

    from functools import lru_cache
    
    @lru_cache(maxsize=None)  # 使用内置的缓存装饰器
    def expensive_function(x):
    
        print(f"Calculating {x}...")
    
        return x * 2
    
    print(expensive_function(4))  # 计算并缓存结果
    print(expensive_function(4))  # 从缓存中获取结果

    输出:

    Calculating 4…

    8

    8

    第一次调用时计算并缓存,第二次调用直接返回缓存结果,节省了计算时间。

    装饰器是 Python 中的一种用于修改函数或方法行为的高级特性,功能强大且灵活,这里例举了一些装饰器的原理、应用和代码示例。感兴趣的小伙伴欢迎留言进一步探讨。

    作者:网络风云

    物联沃分享整理
    物联沃-IOTWORD物联网 » Python-装饰器(Decorator)详解

    发表回复