装饰器是一个函数,它接收另一个函数作为参数,并返回一个新的函数。装饰器可以在不修改原始函数代码的前提下扩展或修改其行为。就像给礼物包装一样,礼物本身没有变,但通过包装纸,它看起来不一样,功能更丰富。
基本语法
装饰器的基本语法是使用 @
符号在函数定义之前应用装饰器,如下所示:
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 中非常强大的特性,可以帮助我们扩展或修改函数的行为,提高代码的可重用性和可维护性。