介绍装饰器,首先要了解一下在 python 中什么是 callable
在 Python 中,callable 是一个描述 “可被调用” 的概念 —— 任何可以通过 () 语法执行的对象都被称为 “可调用对象”(callable object)。简单来说,当你能写出 obj() 这样的代码而不报错时,obj 就是一个可调用对象。
判断一个对象是否可调用,可使用内置函数 callable(obj):返回 True 表示可调用,False 表示不可调用。
常见可调用对象类型:
def func():
pass
print(callable(func)) # True(函数可调用)
print(callable(lambda x: x+1)) # True(lambda 可调用)
class MyClass:
pass
print(callable(MyClass)) # True(类可调用,MyClass() 会创建实例)
__call__ 方法的类才可调用)class Counter:
def __init__(self):
self.count = 0
def __call__(self): # 实现 __call__ 方法
self.count += 1
return self.count
c = Counter()
print(callable(c)) # True(实例可调用)
print(c()) # 1(调用实例,执行 __call__ 方法)
python的装饰器,本质上是一个函数或类,用于在不修改被装饰对象源代码和调用方式的前提下,为其添加额外的功能,其核心机制完全依赖于 “可调用对象” 的特性
装饰器本身必须是可调用对象,且它接收的参数和返回的结果也通常是可调用对象。直白的说,装饰器的参数和返回值一般上都是函数
python 中一切皆对象 object,函数也是一个 object
既然函数只是一个普通的对象,当然也可以作为参数或返回值
函数作为参数传递:
def double(x):
return x * 2
def triple(x):
return x * 3
def calc(func, x):
print(func(x))
calc(double, 3) # 6
calc(triple, 3) # 9
函数作为返回值:
def get_multiple(n):
def multiple(x):
return n * x # 这里的 n 就是最外层函数传进来的 n
return multiple
double = get_multiple(2)
triple = get_multiple(3)
print(double(3)) # 6
print(triple(3)) # 9
装饰器本身就是一个callable,@后的名字就是函数名,可以简单理解为,装饰器不过是一个输入输出都是函数的函数。
既然装饰器是callable,那么当然也可以是函数、类以及实现了
__call__的实例,上面所说的只是最初步的理解!
下面是一个极简的装饰器,这个装饰器中没有任何内容:
def dec(f):
pass
@dec
def double(x):
return x * 2
print(double(3))
完全等价于
def dec(func):
pass
def double(x):
return x * 2
double = dec(double)
print(double(3)) # 6
从上面的等价关系可以看出,装饰器仅仅是一个语法糖而已。
下面以一个函数执行计时器 装饰器为例:
import time
def timeit(func):
def wrapper(x):
start = time.time()
ret = func(x)
print(time.time() - start)
return ret
@timeit
def my_fuc(x):
time.sleep(x)
my_func(1) # 1.00101 至少一秒
下面介绍不同类型的装饰器
函数作为装饰器,接收参数为函数对象,返回同样也是函数对象
# 装饰器本身是一个函数(可调用)
def log_decorator(func): # 接收可调用对象 func 作为参数
def wrapper(): # 返回一个新的可调用对象(函数)
print("调用前日志")
func() # 调用原函数(可调用)
print("调用后日志")
return wrapper
@log_decorator # 等价于:hello = log_decorator(hello)
def hello():
print("hello")
hello() # 调用的是 wrapper(可调用),输出:
# 调用前日志
# hello
# 调用后日志
这里,log_decorator(装饰器)、func(被装饰对象,即参数)、wrapper(返回值)都是可调用对象,缺少任何一个的 “可调用性”,装饰器机制都会失效。
类是可调用对象(调用时创建实例),因此也可以作为装饰器。它接收被装饰的可调用对象,通过实例的 __call__ 方法实现增强(此时实例需是可调用对象)。
# 装饰器是一个类(可调用)
class TimerDecorator:
def __init__(self, func): # 接收可调用对象 func 作为参数
self.func = func
def __call__(self): # 实例可调用,实现增强逻辑
import time
start = time.time()
self.func() # 调用原函数(可调用)
end = time.time()
print(f"耗时:{end - start}s")
@TimerDecorator # 等价于:run = TimerDecorator(run)(创建实例,run 变为实例)
def run():
print("运行中...")
time.sleep(1)
run() # 调用实例(可调用),执行 __call__ 方法,输出:
# 运行中...
# 耗时:1.001s
这里,TimerDecorator(类,可调用)接收 run(函数,可调用),返回其实例(因实现 __call__ 而可调用),最终 run 被替换为这个可调用实例。
带参数的装饰器本质是 “可调用对象的嵌套”:外层函数接收参数,返回一个装饰器(可调用),内层装饰器再接收被装饰对象。
# 带参数的装饰器:外层函数接收参数,返回一个可调用的装饰器
def repeat(times): # 外层:接收参数,返回装饰器
def decorator(func): # 内层:装饰器(可调用),接收被装饰函数
def wrapper(): # 返回可调用的 wrapper
for _ in range(times):
func()
return wrapper
return decorator # 返回内层装饰器(可调用)
@repeat(times=3) # 等价于:hello = repeat(3)(hello)
def hello():
print("hello")
hello() # 调用 wrapper(可调用),输出 3 次 hello
这里,repeat(外层函数,可调用)返回 decorator(可调用),decorator 接收 hello(可调用)并返回 wrapper(可调用),每一层都依赖 “可调用性”。