声明:此文章为,python装饰器详解——中篇,上一篇文章中,即详解装饰器——上篇 ,已经详细讲解了装饰器诞生的背景,装饰器的定义、作用、应用场景,本文将以实际例子为依托,深入详解装饰器的各类实现(包括函数装饰器、类装饰器、闭包、装饰器的嵌套四大块内容)系列文章共分为 上、中、下 三篇。此为第二篇。
前面提到过,装饰器分为函数装饰器、类装饰器,本节详细解释函数装饰器,又分为两种情况,因为函数装饰器可以装饰函数,也可以装饰类。函数装饰器是最常见的,故而最先讨论。
注意:因为没有参数,且无函数返回值的函数是最为简单的,就如“上篇”所讨论的那样,这里就不再叙述了,本文主要讲的都是函数带有参数,而且具有函数返回值的函数。
1、函数装饰器 装饰一个函数
假定有一个函数实现两个数的相加,a+b,但是为了对这两个数相加的结果进行加密,我们需要给函数添加额外的代码,但是我们通过装饰器去实现,要达到的效果是,不是直接返回a+b的结果,而是进行进一步处理。代码如下:
def MethodDecoration(function): #外层decorator
c=150
d=200
def wrapper(a,b): #内层wrapper。和add_function参数要一样
result=function(a,b)
result=result*c/d #加密,相当于添加额外功能
return result #此处一定要返回值
return wrapper
@MethodDecoration
def add_function(a,b):
return a+b
result=add_function(100,300) #函数调用
print(result)
运行结果为:300 (即(100+300)*150/200)
因为函数装饰器去装饰函数最为常见,所以这里就不多再解释了,按照前面上篇所讲的模板来即可,但是因为被装饰的函数有参数,而且具有返回值,有两个点需要注意的,
第一:wrapper需要保证与add_function参数一致。因为返回的wrapper就是add_function,所以要统一,我们可以使用*arg,和**args去匹配任何参数;
第二:wrapper一定要返回值。因为add_function函数是需要返回值的。
2、函数装饰器 装饰一个类
在Python中,从某种意义上来说,函数和类是一样的,因为它们都是对象(python一切皆对象),故而decorator的参数理所当然也可以传入一个类了。其中最经典的应用,就是使用装饰器构造“单例模式”(不明白单例模式的小伙伴可以参见下面这篇博文哦)
一文详解“单例模式”及其python语言的实现
代码如下:
def Singleton(cls): #这是第一层函数,相当于模板中的Decorator.目的是要实现一个“装饰器”,而且是对类型的装饰器
'''
cls:表示一个类名,即所要设计的单例类名称,
因为python一切皆对象,故而类名同样可以作为参数传递
'''
instance = {}
def singleton(*args, **kargs): #这是第二层,相当于wrapper,要匹配参数
if cls not in instance:
instance[cls] = cls(*args, **kargs) #如果没有cls这个类,则创建,并且将这个cls所创建的实例,保存在一个字典中
return instance[cls] #返回创建的对象
return singleton
@Singleton
class Student(object):
def __init__(self, name,age):
self.name=name
self.age=age
s1 = Student('张三',23)
s2 = Student('李四',24)
print((s1==s2))
print(s1 is s2)
print(id(s1),id(s2),sep=' ')
上面运行的结果是:
True
True
2150787003840 2150787003840
懂得单例模式的小伙伴可能一看就明白了,上面的实现和我前面讲过的“装饰器模板”,基本上一样,第一层、第二层、返回值、参数匹配等。但是有的小伙伴可能会问,这里我没有看到“添加额外功能”啊,装饰器不是添加额外功能的么?实际上“添加额外功能”是一种抽象的表述,不是说一定要添加什么东西,对被装饰的对象(函数、类)进行某种约束、处理、添加、删减等额外操作统称为添加额外功能。
这里约束了这个类型Student的创建,主要这个类还没有创建实例,就创建一个,只要创建了,就不在创建新的实例了,只需要把之前创建的返回即可,这是单例模式的思想。如果还是不明白,下面再举一个“添加额外功能”的例子。
比如我有一个学生类,在创建学生实例的时候有两个实例属性,name,age,现在要通过装饰器对类加以装饰,使得在创建学生类的实例的时候,还会添加height和weight两个属性,代码如下:
def ClassDecorator(cls): #第一层函数decorator
height=170
weight=65
def wrapper(name,age): #第二层函数wrapper,参数要和类的构造函数匹配
s=cls(name,age)
s.height=height #添加两个额外属性
s.weight=weight
return s #返回创建的对象,因为类的构造函数是要返回实例的,即有返回值
return wrapper
@ClassDecorator
class Student:
def __init__(self,name,age):
self.name=name
self.age=age
stu=Student('张三',25)
print(stu.name)
print(stu.age)
print(stu.height) #在 IDE中可能会有提示此处错误,学生没有height和weight属性,但是运行之后没错
print(stu.weight) #这就是python的魅力,动态添加属性
运行结果为:
张三
25
170
65
上面的例子和我们前面讲的装饰函数实在是太像了,基本上和前面讲的模板一模一样。
总结:函数装饰器不管是装饰函数、还是装饰类,所遵循的思想原理是一样的,实现的方式也是大同小异。注意,函数装饰器装饰类,实际上是装饰类的构造函数哦!
前面定义的装饰器都是函数,实际上,类也可以是一个装饰器,同样的道理,类装饰器既可以装饰函数,也可以装饰类。
1、类装饰器 装饰函数
先从一个简单的例子说起,代码如下:
class MethodDecorator:
def __init__(self,function):
self.function=function
def __call__(self):
print('开始')
self.function()
print('结束')
@MethodDecorator
def myfunc():
print('我是函数myfunc')
myfunc()
运行结果如下:
开始
我是函数myfunc
结束
可能有的小伙伴很懵逼,怎么会得到这样的结果?我们一句一句来分析。
@MethodDecorator
def myfunc():
pass
myfunc()#调用函数
这里相当于 myfunc=MethodDecorator(myfunc),这样一写就明白了,首先myfunc函数作为参数传递给类的构造函数,因为调用类的构造函数自然会返回类的一个实例对象,所以前面的myfunc实际上是类的一个实例对象,然后调用myfunc,这里虽然从形式上看依然是看起来还是调用函数,但是本质上已经发生了变化,它实际上一个对象调用(这是类装饰器的本质,很重要),这就是为什么要定义__call__魔法方法的原因。下面比如函数有返回值,而且有参数,要用类装饰器去装饰这个函数,再用一个实例说明,依然以上面的两个数据值和进行加密为例。
class MethodDecorator:
def __init__(self,function): #这里相当于是第一层,作用是将函数function传递进来
self.function=function
self.c=150 #这两个是需要加密的额外信息
self.d=200
def __call__(self,a,b): #这相当于是第二层的wrapper
print('开始')
result=self.function(a,b)
result=result*self.c/self.d
print('结束')
return result #返回值
@MethodDecorator
def add_function(a,b):
return a+b
result=add_function(100,300) #这里相当于是函数调用
print(result)
运行结果为:
开始
结束
300.0
总结:实际上类装饰器所实现的功能在原理上和函数装饰器也没有太大的区别,但是在代码实现上有所区别,主要体现在两方面:
第一:类装饰器的构造函数__init__就相当于是第一层(外层)的decorator,传入需要装饰的对象作为参数;
第二:类装饰器的魔法方法__call__相当于是第二层(内层)的wrapper,注意参数要统一,有返回值需要返回值。
2、类装饰器 装饰类
依然以上面的给学生添加额外属性为例
class ClassDecorator:
def __init__(self,cls): #这里相当于是第一层,作用是将类名Student传递进来
self.cls=cls
self.height=170
self.weight=65
def __call__(self,name,age): #这相当于是第二层的wrapper
s=self.cls(name,age)
s.height=self.height #动态添加属性,增加额外信息
s.weight=self.weight
return s #返回创建的学生实例s
@ClassDecorator
class Student:
def __init__(self,name,age):
self.name=name
self.age=age
stu=Student('张三',25) #注意:这里的Student其实并不是一个类了,而是装饰器返回的一个对象,即这里的Student是ClassDecorator的实例
#而且,这里的Student('张三',25) 也不是构造函数了,它的本质是装饰类的“对象调用”
print(stu.name)
print(stu.age)
print(stu.height)
print(stu.weight)
运行结果为:
张三
25
170
65
总结:这里的Student其实并不是一个类了,而是装饰器返回的一个对象,即这里的Student是ClassDecorator的实例,而且,这里的Student('张三',25) 也不是构造函数了,它的本质是装饰类的“对象调用”。
通过上面的例子,不管类装饰器是装饰类,还是装饰函数,它的模板都是大同小异的,如下所示:
class ClassDecorator: #类装饰器的名称
def __init__(self,function_or_cls): #这里相当于是第一层,作用是将需要装饰的类名、或者是函数名传递进来
#这里可以添加额外信息
self.cls=cls #或者是self.function=function,本质是要构造 一个属性
#这里可以添加额外信息
def __call__(self,name,age): #这相当于是第二层的wrapper,参数需要与被装饰的类、被装饰的函数,参数相同
#这里可以增加额外信息
s=self.cls(name,age) #本质是调用原来的函数或者类的构造函数
#result=self.function(a,b)
#这里可以增加额外信息
return s #返回创建的学生实例s
注意:类装饰器,对象调用__call__是不可或缺的哦。
前面讲了一大堆装饰器的优点:简化代码,代码复用;增加额外功能等。那么装饰器优缺点吗,当然有了,世界上就没有完美无缺的东西!那到底有一些什么样的缺点呢?其实在上面的表述中已经提到了,不知道小伙伴有没有注意!
1、函数装饰器 装饰函数的时候(参见上面的代码,这里就不重复一遍了)
在上面源代码的基础之上添加依据下面的代码:
print(add_function.__name__)
返回的结果是:wrapper
这是为什么,如果add_function没有被装饰器修饰的话,则返回的应该为add_function,这里为什么会返回第二层包装函数wrapper的名称?这是因为装饰器的本质是add_function=MethodDecoration(add_function),而MethodDecoration返回的本来就是wrapper,这就是上面结果的解释了。
2、函数装饰器 装饰类的时候(参见上面的代码,这里就不重复一遍了)
同样添加一句代码
print(Student.__name__)
返回的结果是:wrapper
出现这个现象的原因同上面1中所述的,完全一样。
3、类装饰器 装饰函数的时候(参见上面的代码,这里就不重复一遍了)
同样的添加下面一句话
print(add_function.__name__) #这里IDE不会提示错误哦,IDE依然觉得这是个函数,应该有__name__才对的
显示:AttributeError: 'MethodDecorator' object has no attribute '__name__'。
这里为什么突然不一样了呢?正如前面所说的,这里的add_function本质上是add_function=MethodDecorator(add_function),所以add_function本质上是装饰类的一个实例,而MethodDecorator没有定义__name__属性,那自然调用add_function.__name__就会显示没有__name__这个属性了。
4、类装饰器 装饰类的时候(参见上面的代码,这里就不重复一遍了)
print(Student.__name__) #这里IDE不会提示错误哦,IDE依然觉得这是个类名,应该有__name__才对的
显示:AttributeError: 'ClassDecorator' object has no attribute '__name__'
原因同上面一样,因为Student本质上是ClassDecorator的一个对象实例哦!
(1)被函数装饰器所装饰的对象(函数、类)已经不再是它本身了,虽然从形式上看没有变化,本质上是函数装饰器的内部wrapper;
(2)被类装饰器所装饰的对象(函数、类)也不再是它本身了,虽然从形式上看没有变化,本质上是类装饰器的一个对象。
补充:关于装饰器的嵌套,装饰器与python闭包的关系,我会在系列文章:Python高级编程——装饰器Decorator详解(下篇)中继续讲解,有兴趣的继续关注!