装饰器(Decorator)是 Python 中一个非常实用的功能,它可以让你在不改变原有代码功能的前提下,为函数或类添加新的功能。这就像在装扮你的房间时,你可以用装饰品来改变房间的外观和风格,而不需要改变房间的内部结构。
装饰器又名(函数/类)装饰器,顾名思义,就是装饰(函数/类)的,用来增强(函数/类)的功能。
装饰器运行的顺序
被装饰函数(inner)在哪里执行要看你在哪里调用(只会执行一次)。最先执行的是outer,当outer运行完成后,在return的地方执行了wrapper。
下面是一个简单的例子,用装饰器来记录函数执行时间:
import time
def timer(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:.6f} seconds to execute.")
return result
return wrapper
@timer
def my_func():
time.sleep(1)
my_func() # 输出:Function my_func took 1.001149 seconds to execute.
在这个例子中,我们定义了一个装饰器 timer,它接收一个函数作为参数,并返回一个新的函数 wrapper。新函数 wrapper 记录了原函数的执行时间,并在执行原函数后输出执行时间。最后,我们使用 @timer 语法将装饰器应用到函数 my_func 上,这样每次调用 my_func 时就会自动记录执行时间。
装饰器的作用非常广泛,可以用它来实现缓存、日志记录、权限校验、性能分析等功能。它可以让你的代码更加简洁、优雅和可维护。
除了上面的例子,下面还有一些常见的装饰器用法和示例:
类型提示
def typed(func):
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with {args} and {kwargs}")
return func(*args, **kwargs)
return wrapper
@typed
def greet(name: str, age: int) -> None:
print(f"Hello, my name is {name} and I'm {age} years old.")
greet("Alice", 25) # 输出:Calling greet with ('Alice', 25) and {}
在这个例子中,我们定义了一个装饰器 typed,它接收一个函数作为参数,并返回一个新的函数 wrapper。新函数 wrapper 打印传入的参数,并调用原函数。原函数 greet 带有类型提示,它接受一个字符串类型的 name 和一个整数类型的 age,并返回 None。最后,我们使用 @typed 语法将装饰器应用到函数 greet 上,这样每次调用 greet 时就会打印传入的参数。
权限校验
def check_permission(func):
def wrapper(*args, **kwargs):
if not current_user.can_access(func.__name__):
raise PermissionDeniedException()
return func(*args, **kwargs)
return wrapper
@check_permission
def delete_post(post_id: int) -> None:
database.delete_post(post_id)
在这个例子中,我们定义了一个装饰器 check_permission,它接收一个函数作为参数,并返回一个新的函数 wrapper。新函数 wrapper 校验当前用户是否有执行原函数的权限,如果没有则抛出异常。原函数 delete_post 接收一个整数类型的 post_id,并从数据库中删除对应的帖子。最后,我们使用 @check_permission 语法将装饰器应用到函数 delete_post 上,这样每次调用 delete_post 时就会校验权限。
缓存
from functools import wraps
def cached(func):
cache = {}
@wraps(func)
def wrapper(*args, **kwargs):
if args not in cache:
cache[args] = func(*args, **kwargs)
return cache[args]
return wrapper
@cached
def factorial(n):
if n == 0:
return 1
else:
return n * factorial(n-1)
print(factorial(5)) # 输出:120,计算结果缓存在cache中,第二次调用时直接返回缓存结果
在这个例子中,我们使用了 functools.wraps 来保留原函数的元数据,定义了一个装饰器 cached,它接收一个函数作为参数,并返回一个新的函数 wrapper。新函数 wrapper 使用一个字典 cache 来缓存计算结果,如果传入的参数不在缓存中,则调用原函数计算结果并缓存起来,否则直接返回缓存结果。原函数 factorial 计算阶乘,使用装饰器 @cached 将它应用到函数上,这样每次调用 factorial 时都会使用缓存结果,避免重复计算。
日志记录
def log(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__} with {args} and {kwargs}")
result = func(*args, **kwargs)
print(f"Result: {result}")
return result
return wrapper
在Python中,我们将会在很多地方看到装饰器的应用场景,比如Python中的 @classmethod、@staticmethod、上面例子中使用的 @functools.wraps(func) 等都使用到了装饰器,另外很多第三方库中也会有大量的装饰器应用。
通过使用装饰器,我们不仅让其他函数在不做任何代码改动的情况下增加额外的功能,同时减少了很多重复代码,让代码更加Pythonic。