python进阶——类(五)类装饰器

(八)类装饰器

在阅读本篇博客之前,需要对装饰器有一个基础的了解,请先阅读python进阶——函数(三) - ZZHの个人博客 (07xiaohei.com)中关于函数的装饰器内容。

1. 把装饰器写在类里:

严格来说是函数装饰器的内容,只是作为装饰器的函数被写在了类中,同时用于修饰其他函数。

简单的例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class test:
def debbug(self,function):
def get_para(*args,**kwargs):
print("start debbug")
function(*args,**kwargs)
print("finish debbug")
return get_para
t=test()
@t.debbug
def add_test(a,b):
print("add result:",a+b)
add_test(3,4)
# 运行结果:
# start debbug
# add result: 7
# finish debbug

注意,此时的实例方法因为没有实例化无法使用,所以必须先创建一个实例再将该实例的对应装饰器方法绑定给被装饰的函数。

实际上可以直接创建类方法跳过实例化步骤完成绑定,稍后会讲解。

除了装饰类外的函数,也可以装饰同一个类内的函数:

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
class test:
def __init__(self):
self.i1=10
self.i2=12
def info(func):
def get_para(self,*args,**kwargs):
print("start info")
result =func(self, *args, **kwargs)
self.i1 += 10
print(result)
print("end info")
return result
return get_para
@info
def func(self):
return self.i1+self.i2
t=test()
print(t.i1)
print(t.func())
print(t.i1)
# 运行结果:
# 10
# start info
# 22
# end info
# 22
# 20

2. 类作为装饰器使用:

类作为装饰器,需要保证已经定义了__call__方法,这样类才可以作为装饰器,并在装饰函数时运行__call__方法的内容——也就是把类中的__call__方法作为一个装饰器函数来使用了。

简单的例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class test:
def __init__(self,func):
self.rate=0.8
self.func=func
def __call__(self,*args,**kwargs):
print("class decorator:")
print("class rate:",self.rate)
result =self.func(self.rate,*args,**kwargs)
return result
@test
def mulfunc(r,a,b):
return r*a*b
res=mulfunc(2,3)
print(res)
# 运行结果:
# class decorator:
# class rate: 0.8
# 4.800000000000001

__init__方法将函数进行了实例化,此时可以在任意位置使用self.func调用函数,但是只有在__call__中定义的方法会被装饰到函数中,同时也可以为其传入相关的参数。

除了装饰一般的函数,也可以用于装饰另一个类的方法——此时要将__call__方法修改为__get__方法,这相当于把另一个类的实例方法变成了属性,调用时不需要加()。

__get__方法涉及到了Python中的描述器机制,其机制比较复杂,这里不进行介绍,只是使用,如有兴趣可以自行了解,如果之后使用频繁也可能单开一篇博客讲解。

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 test:
def __init__(self,func):
self.rate=0.8
self.func=func
def __get__(self,otherself,owner,*args,**kwargs):
print(otherself)
print(owner)
print("class decorator:")
print("class rate:", self.rate)
result = self.func(otherself,self.rate,*args,**kwargs)
return result
class use_test:
def __init__(self,a,b):
self.a=a
self.b=b
@test
def use(self,r):
return r*self.a*self.b
ut=use_test(3,4)
print(ut.use)
# 运行结果:
# <__main__.use_test object at 0x0000014AD8A188D0>
# <class '__main__.use_test'>
# class decorator:
# class rate: 0.8
# 9.600000000000001

可以看到,另一个类的实例方法作为对象被传给了test,__get__方法的第一个参数是类自身,第二个otherself参数代表着实例化的对象,第三个owner代表实例的类本身,use方法被作为属性调用和输出了。

3. 为类增加装饰器:

类本身也可以增加装饰器,相当于在实例化开始进行了额外的操作。

可以用一个类或者函数作为装饰器。

函数作为装饰器,可以输出信息,可以为被装饰的类增加属性或者方法,甚至直接重写类。

类也可以直接作为装饰器修饰另一个类,注意要定义__call__方法。

简单的例子如下:

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
def function_add(func):
def create_use(class_obj):
print(class_obj)
class_obj.add_val=10
class_obj.add_func=func
return class_obj
return create_use
def add_print(self,a,b):
print("a+b=",a+b)
@function_add(add_print)
class test:
pass
t=test()
print(t.add_val)
t.add_val+=10
print(t.add_val)
t.add_func(3,4)
tt=test()
print(tt.add_val)
# 运行结果:
# <class '__main__.test'>
# 10
# 20
# a+b= 7
# 10

def change_class(class_obj):
class newclass:
i=10
return newclass
@change_class
class test:
i=5
t=test()
print(t.i)
# 运行结果:
# 10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class animal:
def __init__(self,class_obj):
print(class_obj)
self.class_obj=class_obj
def __call__(self, *args, **kwargs):
print("传入args=",args)
print("create an animal!")
res =self.class_obj(*args, **kwargs)
return res
@animal
class cat:
def __init__(self,name):
self.name=name
def eat(self):
print(self.name+" is eating!")
c=cat("小喵")
c.eat()
# 运行结果:
# <class '__main__.cat'>
# 传入args= ('小喵',)
# create an animal!
# 小喵 is eating!

4. Python内置常用装饰器:

内置的装饰器和普通装饰器的原理是一样的,但返回的不是函数,而是类对象。

下面对最常用的五个装饰器分别进行阐述。

(1)@abstractmethod

如果学习过c++语言的话,会知道一个叫做"抽象类"的概念,这种类不能被直接创建对象,必须被继承且实现了对应的虚函数才能创建,Python中实现这一机制就是通过@abstractmethod这个装饰器完成的。

@abstractmethod用于对程序接口的控制,含有@abstractmethod的基类不能实例化,继承的派生类必须实现@abstractmethod装饰的方法。

使用@abstractmethod需要内置库abc,abc提供了抽象类ABC和@abstractmethod装饰器,想要使用抽象类必须直接或者间接继承ABC才能定义。

简单的例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
from abc import ABC,abstractmethod
class abs_class(ABC):
def __init__(self,i):
self.i=i
@abstractmethod
def test(self):
pass
class son1(abs_class):
def test(self):
print("test")
class son2(abs_class):
def testt(self):
print("testt")

定义的abs_class为抽象基类,son1和son2是抽象类的派生类,son1完成了test方法的覆盖,son2则没有。

因此,实例化abc_class和son2均会报错。

错误如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
a =abs_class()
# Traceback (most recent call last):
# File "D:\pycharmwork\blog_use.py", line 3799, in <module>
# a =abs_class()
# ^^^^^^^^^^^
# TypeError: Can't instantiate abstract class abs_class with abstract method test

s2=son2()
# Traceback (most recent call last):
# File "D:\pycharmwork\blog_use.py", line 3805, in <module>
# s2=son2()
# ^^^^^^
# TypeError: Can't instantiate abstract class son2 with abstract method test

s1=son1(5)
s1.test()
# 运行结果:
# test

(2)@property

property用于装饰方法,被装饰的方法不可在类被实例化后被调用,只能通过访问与函数同名的属性进行调用——相当于把方法伪装成了一个属性。

伪装的属性和普通属性不同,需要自行实现一个实例属性的get、set和delete的内部逻辑,此实现是通过属性的装饰器setter、getter和deleter完成的。

getter装饰器和不带getter的属性装饰器效果一致,所以可以不定义getter装饰器。

而没有实现setter或者deleter的话,是一个只读属性,可以用于Python的安全访问。

@property将返回的函数变味了一个property对象

被装饰的方法内部可以实现处理逻辑,对外又提供统一的调用方式。

简单的例子如下:

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
class test:
def __init__(self,i):
self.number1=i
self.number2=i*i
@property
def num(self):
return self.number1
@property
def num2(self):
return self.number2
@num2.setter
def num2(self,temp):
self.number2=temp
@num2.deleter
def num2(self):
del self.number2

t=test(5)
print(t.num)
print(t.num2)
t.num2+=20
print(t.num2)
print(t.__dict__)
del t.num2
print(t.__dict__)
# 运行结果:
# 5
# 25
# 45
# {'number1': 5, 'number2': 45}
# {'number1': 5}

上述代码中,num方法伪装成了属性,对应的是number1属性,此时没有定义setter和deleter的装饰器,所以不能修改和删除该属性,只读;而num2方法伪装成属性时定义了两个装饰器,这导致了number2属性的值可以被修改或者删除。

(3)@staticmethod

是把函数嵌入到类中的一种方式,增加@staticmethod的类函数被声明为类的静态函数,静态函数是独立于类的,不需要类的实例化就可以直接被调用,也因此被装饰的函数不需要代表对象实例的self参数。

可以把这种函数看作是寄存在类名下的普通函数。

类似于c++中的静态成员函数。

简单的例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class test:
j=100
def __init__(self):
self.i=100
@staticmethod
def outertest(class_obj):
print(class_obj.j)
class test2:
j=200
t=test()
t2=test2()
t.outertest(t)
t.outertest(t2)
test.outertest(t)
# 运行结果:
# 100
# 200
# 100

可以看到,outertest函数的可以不通过实例,也可以利用实例,不能直接调用和使用类内的属性,仍然需要传入类实例对象才可以完成对属性的操作,而且没有self参数,也可以传入其他类实例对象完成操作。

如果想要完成不实例化就操作类的类属性(实例属性一定要实例化),需要配合类方法完成。

下面介绍类方法。

(4)@classmethod

被@classmethod装饰的方法是类方法,类方法的调用可以通过类,也可以通过实例。

类方法只是用于类的,只能访问类属性,并完成一定的处理,不能访问实例属性。

这种方法必须有一个参数,一般用cls命名,代表类本身,调用时第一个参数隐性传递该参数,不需要主动传入。

同样类似于c++中的静态成员函数。

@classmethod和@staticmethod区别主要在于,前者主要用于该类的静态属性的操作,而后者则是与实例无关但与类的封装功能有关的函数。

简单的例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
class test:
i=10
@classmethod
def square_i(cls):
cls.i=cls.i**2
return cls.i
print(test().square_i())
t=test()
print(t.square_i())
# 运行结果:
# 100
# 10000

可以把@classmethod的方法和@staticmethod的函数联合使用完成一些特定功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class test:
i=10
def __init__(self):
self.j=100
@classmethod
def square_i(cls):
cls.i=cls.i**2
return cls.i
@staticmethod
def test_use(class_name):
return test.square_i()*class_name.j
class test2:
j=200
t=test()
t2=test2()
print(test.test_use(t))
print(test.test_use(t2))
# 运行结果:
# 10000
# 2000000
(5)@wraps

先来看一段简单的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def debbug(func):
def get_para(*args,**kwargs):
print("start debbug")
result =func(*args,**kwargs)
print("end debbug")
return result
return get_para
@debbug
def use_de():
print(use_de.__name__)
use_de()
# 运行结果:
# start debbug
# get_para
# end debbug

装饰器在修饰函数时,可以看到,use_de的__name__名称并不是use_de,而是get_para,说明装饰器"污染"了被装饰函数的属性,被装饰函数的函数名和属性是属于新函数的。

因此,为了保证被装饰器修饰后的函数还有原来的属性,需要使用functools.wraps装饰器来保证被装饰函数的函数名和属性不会发生改变。

修改的例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from functools import wraps
def debbug(func):
def get_para(*args,**kwargs):
print("start debbug")
result =func(*args,**kwargs)
print("end debbug")
return result
return get_para
@debbug
def use_de():
print(use_de.__name__)
use_de()
# 运行结果:
# start debbug
# get_para
# end debbug

可以看到,结果发生了改变。


python进阶——类(五)类装饰器
http://example.com/2023/07/23/python进阶——类(五)类装饰器/
作者
07xiaohei
发布于
2023年7月23日
许可协议