没有理想的人不伤心

Python - decorator深度解析

2025/06/13
2
0

1 装饰器(decorator)

  • 装饰器的核心机制依赖于 callable:
    • 装饰器本身必须是可调用的(才能接收被装饰对象);
    • 被装饰的对象必须是可调用的(才能被传递给装饰器并在内部调用);
    • 装饰器返回的结果必须是可调用的(才能替代原对象被后续代码调用)。

1.1 Callable

介绍装饰器,首先要了解一下在 python 中什么是 callable

在 Python 中,callable 是一个描述 “可被调用” 的概念 —— 任何可以通过 () 语法执行的对象都被称为 “可调用对象”(callable object)。简单来说,当你能写出 obj() 这样的代码而不报错时,obj 就是一个可调用对象。

判断一个对象是否可调用,可使用内置函数 callable(obj):返回 True 表示可调用,False 表示不可调用。

常见可调用对象类型

  1. 函数
def func():
    pass
print(callable(func))  # True(函数可调用)
print(callable(lambda x: x+1))  # True(lambda 可调用)
class MyClass:
    pass
print(callable(MyClass))  # True(类可调用,MyClass() 会创建实例)
  1. 类的实例(实现了 __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__ 方法)
  1. 内置可调用对象,如int、len等

1.2 装饰器

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 至少一秒

下面介绍不同类型的装饰器

1.2.1 函数作为装饰器

函数作为装饰器,接收参数为函数对象,返回同样也是函数对象

# 装饰器本身是一个函数(可调用)
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(返回值)都是可调用对象,缺少任何一个的 “可调用性”,装饰器机制都会失效。

1.2.2 类作为装饰器

类是可调用对象(调用时创建实例),因此也可以作为装饰器。它接收被装饰的可调用对象,通过实例的 __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 被替换为这个可调用实例。

1.2.3 带参数的装饰器

带参数的装饰器本质是 “可调用对象的嵌套”:外层函数接收参数,返回一个装饰器(可调用),内层装饰器再接收被装饰对象。

# 带参数的装饰器:外层函数接收参数,返回一个可调用的装饰器
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(可调用),每一层都依赖 “可调用性”。