logo

装饰器是一个函数,它接收另一个函数作为参数,并返回一个新的函数。装饰器可以在不修改原始函数代码的前提下扩展或修改其行为。就像给礼物包装一样,礼物本身没有变,但通过包装纸,它看起来不一样,功能更丰富。

基本语法

装饰器的基本语法是使用 @ 符号在函数定义之前应用装饰器,如下所示:

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 Python!") say_hello()

上面代码中,my_decorator 就是一个装饰器函数,我们可以看到该函数接收一个函数 func 作为参数,并返回一个新的函数 wrapper,在调用 func 函数之前和之后执行一些代码,所以我们说 my_decorator 装饰了 func 函数。在 say_hello 函数定义之前使用 @my_decorator 语法糖,就相当于将 say_hello 函数以参数形式传递给了 my_decorator 函数,当真正调用 say_hello 函数时,实际上调用的是 wrapper 函数,所以最终输出的结果是:

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

当然除了使用 @ 语法糖之外,我们还可以手动应用装饰器,如下所示:

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 def say_hello(): print("Hello!") say_hello = my_decorator(say_hello) say_hello()

一般情况下我们都会使用 @ 语法糖来应用装饰器,因为这样更简洁明了。

带参数的装饰器

对于带有参数的函数,装饰器同样可以处理,我们只需要在装饰器函数中使用 *args**kwargs 来接收任意参数,并将其传递给原始函数,如下所示:

def my_decorator(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 @my_decorator def greet(name): print(f"Hello, {name}!") greet("Alice")

在上面代码中,greet 函数接收一个参数 name,然后在装饰器函数中我们并没有指定参数,这是因为可能还会有其他函数需要装饰,在 Python 中,函数可以使用 *args**kwargs 来接收任意参数,但是需要注意 args 是一个元组,kwargs 是一个字典,而且 *args 一定要在 **kwargs 前面,否则会报错。这样我们就可以使用装饰器来装饰带参数的函数了。

多个装饰器

同样我们还可以同时应用多个装饰器,如下所示:

def decorator1(func): def wrapper(): print("Decorator 1") func() return wrapper def decorator2(func): def wrapper(): print("Decorator 2") func() return wrapper @decorator1 @decorator2 def say_hello(): print("Hello!") say_hello()

多个装饰器的应用顺序是从内到外,先应用内层装饰器,再应用外层装饰器。上面代码中,say_hello 函数先应用 decorator2 装饰器,再应用 decorator1 装饰器,所以最终输出的结果是:

Decorator 1 Decorator 2 Hello!

保留原始函数信息

使用装饰器后,原始函数的一些信息可能会丢失,比如函数的名称和文档字符串等:

def my_decorator(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 @my_decorator def greet(name): print(f"Hello, {name}!") print(greet.__name__) # 输出: wrapper

上面代码中,greet 函数经过 my_decorator 装饰后,其名称变成了 wrapper,这样会导致一些问题,比如调试时不知道具体是哪个函数出了问题。为了解决这个问题,我们可以使用 functools.wraps 装饰器来保留原始函数的信息:

from functools import wraps def my_decorator(func): @wraps(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 @my_decorator def greet(name): print(f"Hello, {name}!") print(greet.__name__) # 输出: greet

只需要在内层函数上使用 @wraps(func) 装饰器即可,这样就可以保留原始函数的信息了。

常见应用场景

我们已经知道了装饰器的基本用法,那么在实际开发中,什么样的场景适合使用装饰器呢?下面我们来看看一些常见的应用场景,比如日志记录、性能测试、输入验证等,这些场景都可以使用装饰器来实现。

计算函数执行时间

我们可以使用装饰器来计算函数的执行时间,比如下面的例子:

import time def time_it(func): @wraps(func) def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(f"{func.__name__} executed in {end - start} seconds") return result return wrapper @time_it def calculate_sum(n): return sum(range(n)) print(calculate_sum(1000000))

上面代码中,我们定义了一个 time_it 装饰器,用于计算函数的执行时间,然后我们将 calculate_sum 函数应用了这个装饰器,最终输出的结果是:

calculate_sum executed in 0.016825199127197266 seconds 499999500000

通过上面的这个装饰器,我们可以方便地计算任意函数的执行时间。

权限控制

在 Web 开发的时候,经常需要对用户的请求进行权限控制,比如只有管理员才能访问某些页面,我们可以使用装饰器来实现这个功能:

def admin_required(func): @wraps(func) def wrapper(*args, **kwargs): if not is_admin(): return "You are not authorized to access this page" return func(*args, **kwargs) return wrapper @admin_required def admin_page(): return "Welcome to admin page" def is_admin(): # 模拟判断用户是否是管理员 return True print(admin_page())

上面的代码中,我们定义了一个 admin_required 装饰器,用于判断用户是否是管理员,如果不是管理员则返回一个提示信息,这在 Web 开发中是一个非常常见的场景。

打印日志

我们可以使用装饰器来打印函数的输入和输出,方便调试和排查问题:

def log_it(func): @wraps(func) def wrapper(*args, **kwargs): print(f"Calling {func.__name__} with args {args} and kwargs {kwargs}") result = func(*args, **kwargs) print(f"{func.__name__} returned {result}") return result return wrapper @log_it def add(x, y): return x + y print(add(3, 5))

上面的代码中,我们定义了一个 log_it 装饰器,用于打印函数的输入和输出,这样我们就可以方便地查看函数的执行过程。

除了上面的这些场景,装饰器还可以用于缓存、输入验证、异常处理等,可以说装饰器是 Python 中非常强大的特性,可以帮助我们扩展或修改函数的行为,提高代码的可重用性和可维护性。