欢迎光临
我们一直在努力

Python 装饰器进阶:多装饰器、带参装饰器与类装饰器详解

多个装饰器该怎么使用?使用多个装饰器去装饰一个函数

首先我们来定义多个装饰器:

def dec_1(func):
def inner(*args, **kwargs):
print("我是装饰器1,我来装饰这个函数了")
return func(*args, **kwargs)
return inner

def dec_2(func):
def inner(*args, **kwargs):
print("我是装饰器2,我来装饰这个函数了")
return func(*args, **kwargs)
return inner

def dec_3(func):
def inner(*args, **kwargs):
print("我是装饰器3,我来装饰这个函数了")
return func(*args, **kwargs)
return inner

然后再来对函数使用多个装饰器:

@dec_1
@dec_2
@dec_3
def mulFunc():
print("我需要多个装饰器来装饰")

mulFunc()

最后我们来看一下效果如何?

运行结果为:

我是装饰器1,我来装饰这个函数了
我是装饰器2,我来装饰这个函数了
我是装饰器3,我来装饰这个函数了
我需要多个装饰器来装饰

可以看到函数正常运行,能够被多个装饰器装饰,同时不改变函数的源代码和调用方式。

同时可以看到多个装饰器的执行顺序是由内到外的,显示最靠近 mulFunc() 的 @dec_3,然后是 @dec_2,一层一层被包裹,不断被外部的 inner 改变,所以执行顺序是由内到外,先由内部的装饰器改变。

带参数的装饰器

首先装饰器只能接受一个参数,并且是函数类型。不然在实际操作中会报错。

如果你想去接受一个参数,来判断触发哪一种装饰器,而不是一次执行多个装饰器的话,可以使用另外的函数。也就是在装饰器的外层包裹一层函数。

def add_method(*arg, **keyargs):
res = 0
for value in arg:
res += value
for value in keyargs.values():
res += value
return res

def mtl(*arg, **keyargs):
res = 1
for value in arg:
res *= value
for value in keyargs.values():
res *= value
return res

如果是这两个函数,想用一个装饰器来装饰该怎么办?

首先我们写一个能够对应两个的雏形:

def decMulFunc(func):
def inner(*arg, **keyargs):
return func(*arg, **keyargs)
return inner

接着根据需要来进行扩充。首先:

def decMulFunc(func, flag):
def inner(*arg, **keyargs):
return func(*arg, **keyargs)
return inner

这么加肯定是不对的,因为装饰器只能接受一个函数参数。我们可以定义在外面,通过闭包来实现带参数的装饰器:

def judgeFunc(flag):
def decMulFunc(func):
def inner(*arg, **keyargs):
if flag == '+':
print("这是加法")
if flag == '*':
print("这是乘法")
return func(*arg, **keyargs)
return inner
return decMulFunc

我们来解析一下这个闭包加上装饰器的作用以及调用方法:

当我们这样使用时:

@judgeFunc("+")
def add_method(*arg, **keyargs):
res = 0
for value in arg:
res += value
for value in keyargs.values():
res += value
return res

@judgeFunc("*")
def mtl(*arg, **keyargs):
res = 1
for value in arg:
res *= value
for value in keyargs.values():
res *= value
return res

add_method(5, 6) 如果调用这个函数,其中背后到底会执行一些什么?

执行结果:

这是加法
11

可以看到,执行时正确,而且成功加上了装饰器,而且在使用什么什么的时候做出了判断。

当这个函数执行的时候,首先,@judgeFunc("*") 这一层开始发力了,将 * 这个符号参数传入闭包,def judgeFunc(flag): 也就是在这里,然后进入这个函数体内,然后进入 return decMulFunc 这一层,这里返回的是将函数变成了 decMulFunc,但是这个装饰器的内部函数,又让函数返回的是 inner 对象,最终,还是返回 inner(5,6),flag 被当成闭包的参数进行传递,不会闭包的,其原理和装饰器类似,只不过传递的不是函数对象,而是参数对象。

类装饰器

类装饰器就是把类当做装饰器,去装饰函数,而不是使用装饰器去装饰类。

想到用类去装饰函数,很多同学后续想要直接用类的方法去使用,比如:

# 类装饰器,使用类去装饰函数
class c_dec(object):
pass

@c_dec
def fun_UC():
print("要使用类来装饰我")

但是这个方法是错误的,报错原因为:

TypeError: c_dec() takes no argument

当使用 @c_dec 时,原理是,类会尝试去用装饰器的方法,也就是实例化 fun_UC = c_dec(fun_UC),这个方法就是初始化实例化的方法,但是原有的初始化实例方法不能接收这个对象,所以会失败。

正确一些的写法是增加这个方法:

# 类装饰器,使用类去装饰函数
class c_dec(object):
def __init__(self, func):
pass

这样再去执行,就不会让代码报错。然后想一想,当执行 fun_UC() 会怎么样?

首先我们想一下,如果普普通通地创建一个实例化对象的程序是什么样的?实际上是:

class T1():
pass

t1 = T1()

再次调用 t1() 这样很明显是错误的。

那么要怎么样才能让这个类成为装饰器?这时候我们要实现 __call__ 方法,让函数能够成为对象:

def __call__(self, *args, **kwds):
pass

让这个能够成为类装饰器,接下来是正确地使用方法:

# 类装饰器,使用类去装饰函数
class c_dec(object):
def __init__(self, func):
self.func = func # 让函数成为一个属性,能够被正确调用

def __call__(self, *args, **kwds):
print("类装饰器被调用")
self.func(*args, **kwds) # 再去调用这个func函数对象

当再次调用时会发现,可以正常执行:

@c_dec
def fun_UC():
print("要使用类来装饰我")

fun_UC()

输出结果为:

类装饰器被调用
要使用类来装饰我

func 属性最好是私有的,因为不然无论什么实例对象都能访问到 func,比如 fun_UC.func。可以调成私有的,这样就:

# 类装饰器,使用类去装饰函数
class c_dec(object):
def __init__(self, func):
self.__func = func # 让函数成为一个属性,能够被正确调用

def __call__(self, *args, **kwds):
print("类装饰器被调用")
self.__func(*args, **kwds) # 再去调用这个func函数对象

其他的无法访问到。

至于 func 的详细的功能:self.__func = func 能把传入的函数对象保存到 self 自身实例里面,self.__func 现在就是一个函数对象。

为什么可以写 self.__func()?因为 self.__func 本身是一个函数对象,调用它和调用普通函数一样:self.__func() 等价于:func() 只不过你把它保存在类实例的属性里了。

__func 和双下划线的意义:__func 用双下划线命名时,会被 Python 做"名字重整"(name mangling):

  • 在类内部它是 self.__func
  • 实际上会变成 _c_dec__func
  • 目的只是避免与子类或外部属性名冲突,不是"特殊语法"

# 类装饰器,使用类去装饰函数
class c_dec(object):
def __init__(self, func):
self.__func = func # 让函数成为一个属性,能够被正确调用

def __call__(self, *args, **kwds):
print("类装饰器被调用")
self.__func(*args, **kwds) # 再去调用这个func函数对象

是正确写法:

  • self.__func:存储了被装饰的函数对象
  • self.__func():调用这个函数对象
  • 双下划线 __func:只是名字重整,用于隐藏属性,不改变它是"函数对象"的事实

函数之所以能够实现,是因为内部使用了 __call__ 方法。当你写:

@c_dec
def fun_UC():
...

时,先创建装饰器对象:c_dec(fun_UC),将 fun_UC 替换为这个对象的实例,如果类 c_dec 定义了 __call__,那么这个实例就变成"可调用的",也就是:__call__ 会自动执行,去起到一个类似于 inner 内部函数替换的作用。所以参考内部函数的写法,最好要写成 self.__func(*args, **kwds),所以要想使用类去装饰方法,必须要有 __call__ 方法,不然就像装饰器没有内部函数一样没有任何作用。

总结

本文详细介绍了 Python 装饰器的进阶用法:

  • 多个装饰器:可以叠加使用,执行顺序由内到外
  • 带参数装饰器:通过闭包实现,外层函数接收参数,内层函数作为真正的装饰器
  • 类装饰器:通过实现 __init__ 和 __call__ 方法,让类实例可以像函数一样被调用
  • 掌握这些进阶用法,可以让你在 Python 编程中更加灵活地使用装饰器,实现更复杂的逻辑封装和代码复用。

    赞(0)
    未经允许不得转载:171主机测评 » Python 装饰器进阶:多装饰器、带参装饰器与类装饰器详解
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址