Python 装饰器 - 改变已有函数的行为
Python 中的装饰器 (Decorator) 是一种非常强大的编程技巧,它可以在不修改已有函数代码的情况下,增加一些额外的功能。装饰器可以用于很多场景,比如日志、性能分析、权限控制等等。本文将介绍 Python 装饰器的基本用法、高级用法以及常见的应用场景。
1. 基本用法
在 Python 中,函数也是一种对象,可以在运行时动态地改变它们的行为。这一点非常重要,因为装饰器就是利用这一点进行实现的。考虑一个简单的函数:
def hello():
return 'Hello, World!'
我们现在想在每次执行 hello() 函数时,都输出一个时间戳以及调用次数。实现起来很简单,只需要定义一个装饰器函数,用它来包裹 hello() 函数即可:
import time
count = 0
def timing_decorator(func):
def wrapper(*args, **kwargs):
global count
count += 1
print(f"Call #{count}: {func.__name__}() started")
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__}() ended, took {end - start:.5f} seconds")
return result
return wrapper
@timing_decorator
def hello():
return 'Hello, World!'
hello()
输出结果为:
Call #1: hello() started hello() ended, took 0.00000 seconds
可以看到,我们定义了一个名为 timing_decorator 的装饰器函数,并使用 @ 符号将它应用到 hello() 函数上。装饰器函数的输入参数是一个函数对象,返回值也是一个函数对象。在这里,我们定义了一个名为 wrapper 的内部函数,它的参数可以接收任何数量和类型的位置参数和关键字参数。在 wrapper() 函数内部,我们出现了一个 func(*args, **kwargs) 的调用,这就是调用被装饰的函数本身的语句。在 wrapper() 函数内部,我们增加了一些额外的代码,包括一个全局的计数器和时间戳的输出,然后再调用原始的被装饰的函数。最后,将 wrapper() 函数作为返回值,供外界调用。
2. 高级用法
虽然上面的例子已经可以说明装饰器的基本用法,但实际上还有很多高级用法。下面我们将讨论其中的一些用法。
2.1 类装饰器
除了可以用函数去创建装饰器,我们还可以使用类。有些情况下,使用类来实现装饰器可以更加灵活和方便。考虑下面的示例:
class TimingDecorator:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
global count
count += 1
print(f"Call #{count}: {self.func.__name__}() started")
start = time.time()
result = self.func(*args, **kwargs)
end = time.time()
print(f"{self.func.__name__}() ended, took {end - start:.5f} seconds")
return result
@TimingDecorator
def hello():
return 'Hello, World!'
hello()
这里 TimingDecorator 实现了一个类装饰器,它和之前的函数装饰器功能是一样的。
2.2 带参数的装饰器
有时候我们的装饰器可能需要传入参数,这时我们就需要使用嵌套函数来实现带参数的装饰器。考虑下面的例子:
def log(logger):
def decorator(func):
def wrapper(*args, **kwargs):
logger.info(f"Call {func.__name__}()")
return func(*args, **kwargs)
return wrapper
return decorator
import logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
@log(logger)
def hello():
return 'Hello, World!'
hello()
这里我们定义了一个名为 log 的装饰器函数,并将一个 logger 对象作为参数传入。在装饰器内部,我们定义了一个名为 decorator 的嵌套函数,并将被装饰的函数 func 作为参数传入。在 wrapper() 函数中,我们记录了函数的调用信息,并执行了被装饰的函数。最后,返回 wrapper() 函数。
2.3 使用类来实现带参数的装饰器
我们可以使用类来实现带参数的装饰器,并且使用类的方法来传递参数。这样做可以更加方便和灵活。例如:
class Log:
def __init__(self, logger):
self.logger = logger
def __call__(self, func):
def wrapper(*args, **kwargs):
self.logger.info(f"Call {func.__name__}()")
return func(*args, **kwargs)
return wrapper
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
@Log(logger)
def hello():
return 'Hello, World!'
hello()
这个示例和上面的示例的功能是一样的,只不过这次我们使用了类来实现带参数的装饰器。
3. 常见应用
装饰器可以广泛应用于很多场景,其中一些常见的应用场景如下:
3.1 日志
在函数执行的过程中,我们可能希望记录一些调试信息和性能数据。装饰器可以很方便地实现这些功能。例如:
def log(logger):
def decorator(func):
def wrapper(*args, **kwargs):
logger.info(f"Call {func.__name__}()")
return func(*args, **kwargs)
return wrapper
return decorator
import logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
@log(logger)
def hello():
return 'Hello, World!'
hello()
在这个例子中,我们使用了一个名为 log 的装饰器函数,并将一个 logger 对象作为参数传入。在装饰器内部,我们定义了一个名为 decorator 的嵌套函数,并将被装饰的函数 func 作为参数传入。在 wrapper() 函数中,我们记录了函数的调用信息,并执行了被装饰的函数。最后,返回 wrapper() 函数。
3.2 缓存
在一些 IO 密集型的程序中,缓存可以大大提高程序的性能。我们可以使用装饰器来实现缓存功能。例如:
def memoize(func):
cache = {}
def wrapper(*args):
if args in cache:
return cache[args]
else:
result = func(*args)
cache[args] = result
return result
return wrapper
@memoize
def fib(n):
if n < 2:
return n
else:
return fib(n-1) + fib(n-2)
print(fib(10))
这个例子中,我们使用了一个名为 memoize 的装饰器函数。在 memoize 函数内部,我们定义了一个缓存字典 cache,将被装饰的函数 func 作为参数传入。在 wrapper() 函数中,我们先查看 cache 中是否已经有了这个输入参数的结果,如果有,就直接返回缓存的结果,否则就计算出这个输入参数的结果,保存到 cache 中,并返回结果。
3.3 权限控制
在一些 Web 应用程序中,我们
