Python 装饰器

Python 中的装饰器是一种高级语法特性,它允许我们在不修改函数本身代码的情况下增加新功能。装饰器通过使用 @ 符号和一个包裹函数来实现,这个包裹函数接受被装饰的函数作为参数并返回一个新函数。

本文将深入探讨 Python 3 中的装饰器,涵盖以下主题:

  • 什么是装饰器?
  • 如何定义和使用装饰器?
  • 带参数的装饰器?
  • 类装饰器?
  • 内置装饰器?

1. 什么是装饰器?

装饰器本质上是一个高阶函数,它接受一个函数作为参数并返回一个新的修改过的函数。在 Python 中,装饰器允许我们在不直接修改被装饰函数代码的情况下添加额外功能,例如日志记录、权限验证等。

2. 如何定义和使用装饰器?

定义一个简单的装饰器非常容易,我们只需要创建一个包裹函数并将被装饰的函数作为参数传入即可。下面是一个基本示例:

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_whee():
    print("Whee!")

在上面的例子中,我们定义了一个装饰器 my_decorator 和一个被装饰函数 say_whee()。通过使用 @my_decorator 语法糖,Python 会自动调用 my_decorator(say_whee) 并将返回的包裹函数分配给原始函数名称 say_whee。当我们调用 say_whee() 时,实际上是在调用装饰器返回的包裹函数。

3. 带参数的装饰器?

有些情况下,我们需要为装饰器添加一些额外参数以进行更细粒度的控制。这可以通过在装饰器定义中添加另一个包裹函数来实现:

def repeat(num_times):
    def decorator_repeat(func):
        def wrapper(*args, **kwargs):
            for _ in range(num_times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator_repeat

@repeat(3)
def greet(name):
    print(f"Hello {name}")

在上面的例子中,我们定义了一个参数化装饰器 repeat 和一个被装饰函数 greet()。当我们调用 @repeat(3) 时,Python 会先调用 repeat(3) 并返回包裹函数 decorator_repeat。然后将 greet 传递给 decorator_repeat,最终得到的是包裹函数 wrapper

4. 类装饰器?

除了使用函数来定义装饰器外,Python 还允许我们使用类来定义装饰器。这对于在装饰器中保留状态非常有用:

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 function {self.func.__name__}")
        return self.func(*args, **kwargs)

@CountCalls
def say_hello():
    print("Hello!")

在上面的例子中,我们定义了一个类装饰器 CountCalls 和一个被装饰函数 say_hello()。当我们调用 @CountCalls 时,Python 会实例化 CountCalls 类并传递 say_hello 作为参数。由于 CountCalls 有一个 __call__ 方法,因此它是可调用的对象,也就是装饰器。每次调用 say_hello() 时,装饰器都会增加计数并打印消息。

5. 内置装饰器?

Python 标准库中提供了一些有用的装饰器,例如:

  • @property: 将方法转换为只读属性;
  • @classmethod: 将方法绑定到类而不是实例上;
  • @staticmethod: 将方法绑定到类但不需要任何参数。

这些装饰器可以帮助我们编写更清晰、易维护的代码。