0%

python中装饰器与类

如何在类中使用装饰器(类方法装饰器、类装饰器)

基本装饰器写法

python中装饰器语法非常常见,一般有以下几种:

  • 函数装饰器(带参数的、不带参数的、装饰类的、装饰类方法的)
  • 类装饰器(重载类__call__方法的、方法装饰器、类方法装饰器、静态类方法装饰器)

简单来看一下函数装饰器:

  1. 最外层接受参数的装饰器
1
2
3
4
5
6
7
8
9
10
11
12
13
14

## 带参数的函数装饰器

# 最外层接受参数
def dec1(arg):
# 第二层接受函数
def dec2(func):
# 第三层接受函数的参数
def warp(*args):
print("hello")
func(*args)
return warp
return dec2

  1. 不带参数的函数装饰器
1
2
3
4
5
6
7
8
9
10
11

## 不带参数的函数装饰器

# 最外层接受函数
def dec2(func):
# 第二层接受函数的参数
def warp(*args):
print("hello")
func(*args)
return warp

  1. 装饰类的函数装饰器,由于装饰类的时候也有带参数和不带参数的区分,简单起见,这里还是以不带参数为例(带参数也就是多写一层包装函数即可)

装饰类的时候的时候目的是管理类的功能,此时装饰器类似于元类

1
2
3
4
5
6
7
8
9
10
11
12
13

## 装饰类的函数装饰器(假设不带额外参数)

# 最外层用来接受类
def dec2(cls):
# 对cls做一些处理并返回一个class
print("hello")
class NewClass(cls):
def __init__(self):
super().__init__()
self.x = 1
return NewClass

  1. 装饰类方法的函数装饰器,由于装饰类方法的时候也有带参数和不带参数的区分,这里还是以不带参数为例

其实就是多管理一个self对象而已,值得一提的是,warp和func里的self可以没有。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

## 装饰类方法的函数装饰器(假设不带额外参数)

def dec2(func):
# 第二层接受函数的参数,就多一个self
def warp(self, *args):
print("hello")
func(self, *args)
return warp

# 或下面这种写法

def dec2(func):
# 第二层接受函数的参数,就多一个self
def warp(*args):
print("hello")
func(*args)
return warp

上面是基本的函数装饰器的一些写法,下面来看看类装饰器

  1. 重载类__call__方法的类装饰器

此时使用装饰器时需要创建实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

## 重载类__call__方法的类装饰器

class Dec1:
def __init__(self, arg):
self.arg = arg

# 重载__call__方法
def __call__(self, func):
def warp(*args):
print("hello")
func(*args)
return warp

# 使用时需要创建实例
d = Dec1(1)

@d
def f():
print("world")

  1. 方法装饰器类方法装饰器

使用类的一个方法作为装饰器时,需要创建实例;使用类的一个类方法作为装饰器时,不需要创建实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

## 方法装饰器与类方法装饰器

class A:
def dec1(self, func):
def warp(*args):
print("dec1")
func(*args)
return warp

@classmethod
def dec2(cls, func):
def warp(*args):
print("dec2")
func(*args)
return warp

# 方法装饰器需要创建实例
a = A()
@a.dec1
def f():
...

# 类方法装饰器不需要创建实例
@A.dec2
def f():
...

  1. 静态类方法装饰器

静态类方法装饰器不需要处理cls或self对象,速度更快

1
2
3
4
5
6
7
8
9
10
11
12
13
14

class Dec:
@staticmethod
# 注意这里少传入了cls或self
def dec1(func):
def warp(*args):
print("hello")
func(*args)
return warp

@Dec.dec1
def f():
print("world")

由于上述提到的装饰器非常简单,所以就一带而过了。下面我们来看看一些比较实际的应用。

将装饰器封装到类里

加入在项目的构建中有很多装饰器,这些装饰器的功能相似(比如都是用来做一些预处理的),那么我们可以将这些装饰器封装到一个类里,这样就可以方便的管理这些装饰器了。

这些封装在类的装饰器装饰的目标不同时,写法也会有差异

去装饰普通函数

如果一个封装在类的装饰器只是用来装饰普通函数,那么写起来还是比较随意的:用普通的方法封装、用类方法封装还是用静态类方法封装都可以。

区别是普通方法封装需要处理self对象,且要通过实例调用;而类方法封装需要处理cls对象,不需要通过实例调用;静态类方法封装不需要处理cls或self对象,速度更快,也不需要实例。

因此,如果封装在类里面的装饰器只用来装饰普通函数,那么用静态类方法是不错的选择。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

class Dec:
def dec1(self, func):
def warp(*args):
print("dec1")
func(*args)
return warp

@classmethod
def dec2(cls, func):
def warp(*args):
print("dec2")
func(*args)
return warp

@staticmethod
def dec3(func):
def warp(*args):
print("dec3")
func(*args)
return warp

# 普通方法封装要使用实例
dec = Dec()
@dec.dec1
def f1():
...

# 类方法封装不需要使用实例
@Dec.dec2
def f2():
...

# 静态类方法封装不需要使用实例
@Dec.dec3
def f3():
...

去装饰其他类的方法

如果在A类里封装了一些装饰器,用来装饰B类的方法,这个时候只需要仿照之前提到的装饰类的方法的写法即可。

同样,装饰器中的self可写可不写。其实和处理普通函数的装饰器是一样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

class A:
def dec1(self, func):
def warp(*args):
print("dec1")
func(*args)
return warp

@classmethod
def dec2(cls, func):
def warp(*args):
print("dec2")
func(*args)
return warp

@staticmethod
def dec3(func):
def warp( *args):
print("dec3")
func(*args)
return warp

class B:
# 创建实例,以使用实例的方法
a = A()

# 使用实例的方法装饰
@a.dec1
def f1(self):
print(1)

# 使用类方法装饰
@A.dec2
def f2(self):
print(2)

# 使用静态类方法装饰
@A.dec3
def f3(self):
print(3)

去装饰本类的其他方法

这种场景也很容易出现,比如在A类里给出了一个只用于本类的装饰器。而这种场景的问题也是最多的。

首先,先给出一些常见的错误写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

class A:
def dec1(self, func):
def warp(*args):
print("dec1")
func(*args)
return warp

@classmethod
def dec2(cls, func):
def warp(*args):
print("dec2")
func(*args)
return warp

@staticmethod
def dec3(func):
def warp(*args):
print("dec3")
func(*args)
return warp

# 错误写法1
@dec1
def f1(self):
print("1")

# 错误写法2
@A.dec2
def f2(self):
print("2")

# 错误写法3
@A.dec3
def f3(self):
print("3")

我们来分析一下这些错误:

  • 错误写法1:此处是用类普通方法去装饰类普通方法,但是类普通方法必须通过实例调用,此处没有实例,因此会报错。(加self也是不行的,self仅仅是指向实例的,而不创建实例)
  • 错误写法2:此处是用类方法去装饰类普通方法,但是此时A类本身还没有创建出来。即想要定义A类,需要用到一个A类的类方法,陷入了一种循环依赖的情况,因此会报错。
  • 错误写法3:同2

其实正确的写法也很简单,就是在类里面实现一个闭包函数,而这个闭包函数就是我们的装饰器。

1
2
3
4
5
6
7
8
9
10
11
12
13

class A:
# 注意这里没有self
def dec1(func):
def warp(*args):
print("dec1")
func(*args)
return warp

@dec1
def f(self):
print("1")

由于类创建的时候会形成一个命名空间,所以这个闭包函数可以通过命名空间来在类外获取。

由于其本身不是方法,所以不能用实例调用。

1
2
3
4
5
6
7
8
9
10
11

a = A()

@a.dec1 #报错
def f1():
...

@A.dec1 # 可以使用
def f2():
...

如果想要实例和类都可以调用,按如下写法即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

class A:
# 注意这里没有self
def dec1(func):
def warp(*args):
print("dec1")
func(*args)
return warp

@dec1
def f(self):
print("1")

# 在这里使用装饰器的等价调用形式,覆盖掉上面的闭包
dec1 = staticmethod(dec1)

a = A()

@a.dec # 可以使用
def f1():
...

@A.dec # 可以使用
def f2():
...

总结

  • 用来装饰类方法的装饰器写法可以与用来装饰普通方法装饰写法一样,忽略掉cls或self
  • 当A类中的装饰器dec需要装饰A类中的某个方法时,dec应该以闭包形式实现。若dec需要被实例和类方式调用,需要在A类最后以staticmethod形式覆盖掉dec