函数装饰器可以是一个普通函数,也可以是一个可调用类的实例(有__call__()方法的类,此类实例可以像调用函数一样进行小括号运算,自动调用__call__()方法,即有__call__()方法的类实例,称为可调用类的实例)。
函数装饰器可以装饰普通函数,也可以装饰类方法。
类方法被普通函数装饰时,python向self传递隐含的主体实例;
类方法被可调用类实例装饰时,python只传递这个类的实例,可通过显式传递被装饰的类实例来正确调用类方法。
用法
>>> class CountTrace:
def __init__(self,func):
self.calls=0
self.func=func
#实例名()运算,自动调用__call__
# 调用 payraise(0.2)自动调用 CountTrace的 __call__
# 调用 __call__() 时,第1个入参 self 为 CountTrace 类实例
# 而 payraise(self,rate)的self被给了0.2,
# 而未自动传 当前调用的 StaffWithDecorator 类实例
def __call__(self,*args,**kargs):
print('self=',self)
print('*args=',args)
self.calls+=1
print('调用{}{}次'.format(self.func.__name__,self.calls))
return self.func(*args,**kargs)
>>> class StaffWithDecorator:
def __init__(self,name,pay):
self.name=name
self.pay=pay
# payraise 重新绑定为 CountTrace 类实例
# 调用 payraise()相当于调用 CountTrace的 __call__
@CountTrace
def payraise(self,rate):
self.pay*=(1.0+rate)
描述
可调用类实例装饰类方法时,只传可调用类实例给__call__()方法的self,不会自动将装饰类的实例自动传递给*args,导致self.func(*args,**kargs)即调用payraise(self,rate)时,self没有赋值为类StaffWithDecorator的当前实例,赋值成为了0.2,最终rate缺少rate入参。
可以通过显式的将当前实例传递给self来正确调用。
示例
>>> class CountTrace:
def __init__(self,func):
self.calls=0
self.func=func
#实例名()运算,自动调用__call__
def __call__(self,*args,**kargs):
print('self=',self)
print('*args=',args)
self.calls+=1
print('调用{}{}次'.format(self.func.__name__,self.calls))
return self.func(*args,**kargs)
>>> @CountTrace
def testsquare(x):
print(x**2)
>>> testsquare(3)
self= <__main__.CountTrace object at 0x012634D0>
*args= (3,)
调用testsquare1次
9
# 未使用装饰器,正常执行
>>> class StaffNoDecorator:
def __init__(self,name,pay):
self.name=name
self.pay=pay
def payraise(self,rate):
self.pay*=(1.0+rate)
>>> s1=StaffNoDecorator('张三',51000)
>>> s1.payraise(.2)
>>> s1.pay
61200.0
# 有使用装饰器,未传入被装饰方法的实例导致报缺少入参 rate
>>> class StaffWithDecorator:
def __init__(self,name,pay):
self.name=name
self.pay=pay
@CountTrace
def payraise(self,rate):
self.pay*=(1.0+rate)
>>> s1=StaffWithDecorator('张三',51000)
>>> s1.payraise(.2)
self= <__main__.CountTrace object at 0x040D9CF0>
# 只传了0.2,赋值给了self,导致 rate 未传值
*args= (0.2,)
调用payraise1次
Traceback (most recent call last):
File "" , line 1, in <module>
s1.payraise(.2)
File "" , line 11, in __call__
return self.func(*args,**kargs)
TypeError: payraise() missing 1 required positional argument: 'rate'
# 有使用装饰器,被装饰方法的self传成了0.2而非被装饰方法的类实例导致报错
>>> class StaffWithDecorator:
def __init__(self,name,pay):
self.name=name
self.pay=pay
@CountTrace
def payraise(self):
print('self=',self)
self.pay*=(1.0+rate)
>>> s1=StaffWithDecorator('张三',51000)
>>> s1.payraise(.2)
self= <__main__.CountTrace object at 0x01263750>
*args= (0.2,)
调用payraise1次
# self传成了0.2
self= 0.2
Traceback (most recent call last):
File "" , line 1, in <module>
s1.payraise(.2)
File "" , line 11, in __call__
return self.func(*args,**kargs)
File "" , line 8, in payraise
self.pay*=(1.0+rate)
AttributeError: 'float' object has no attribute 'pay'
显式传递payraise的self
>>> class CountTrace:
def __init__(self,func):
self.calls=0
self.func=func
#实例名()运算,自动调用__call__
def __call__(self,*args,**kargs):
print('self=',self)
print('*args=',args)
self.calls+=1
print('调用{}{}次'.format(self.func.__name__,self.calls))
return self.func(*args,**kargs)
>>> class StaffWithDecorator:
def __init__(self,name,pay):
self.name=name
self.pay=pay
@CountTrace
def payraise(self,rate):
self.pay*=(1.0+rate)
>>> s1=StaffWithDecorator('张三',51000)
# 显式将s1传递给payraise()的self
>>> s1.payraise(s1,0.2)
self= <__main__.CountTrace object at 0x03978D50>
*args= (<__main__.StaffWithDecorator object at 0x03978E10>, 0.2)
调用payraise1次
>>> s1.pay
61200.0
描述
使用嵌套函数装饰器,可以装饰普通函数和类方法,并且能正常工作,不需要额外显式送类实例。
嵌套函数装饰类方法时,自动将类实例传递给类方法的self。
示例
>>> def counttrace(func):
calls=0
def wrapper(*args,**kargs):
print('func=',func)
print('args=',args)
nonlocal calls
calls+=1
print('调用 {} {} 次'.format(func.__name__,calls))
return func(*args,**kargs)
return wrapper
>>> @counttrace
def testsquare(x):
print(x**2)
>>> class StaffWithDecorator:
def __init__(self,name,pay):
self.name=name
self.pay=pay
# 嵌套函数装饰类方法,调用时自动将类实例传递给*args到self
@counttrace
def payraise(self,rate):
print('self=',self)
self.pay*=(1.0+rate)
>>> testsquare(3)
func= <function testsquare at 0x03E001E0>
args= (3,)
调用 testsquare 1 次
9
>>> s1=StaffWithDecorator('张三',51000)
>>> s1.payraise(0.2)
func= <function StaffWithDecorator.payraise at 0x03E00390>
args= (<__main__.StaffWithDecorator object at 0x0398D8D0>, 0.2)
调用 payraise 1 次
self= <__main__.StaffWithDecorator object at 0x0398D8D0>
>>> s1.pay
61200.0
描述
在之前的描述符章节学习过,描述符是一个类,它有__get__()、__set__()、__delete__()方法,并且将此类实例赋值给另一个类的属性,进行使用。
__get__()拦截属性的点号运算,__call__()拦截实例的小括号运算。
描述符同时定义call和get时,get方法需要返回一个可调用对象,比如此实例本身,否则无法进行实例的小括号运算。
get方法有三个参数:self,instance,owner。
示例
>>> class GetClass:
# __get__()拦截点号.运算
# 需返回实例本身self
# 才能进行小括号运算
def __get__(self,instance,owner):
print('获取属性')
return self
# __call__()拦截小括号()运算
def __call__(self):
print('可调用类实例')
>>> class TestGet:
gc=GetClass()
>>> tg=TestGet()
# 点号.运算
>>> tg.gc
获取属性
<__main__.GetClass object at 0x0097AED0>
# 点号.运算 和 小括号()运算
>>> tg.gc()
获取属性
可调用类实例
描述
描述符是一个有get、set、delete方法的类,并且将描述符类实例分配其他类的一个属性,会自动拦截此属性的点号、赋值、删除运算。
装饰器编写为一个描述符,被装饰的主体类方法会重新绑定为描述符类实例,进行点号运算和小括号运算时,会自动调用描述符的get和call方法。
描述符可以装饰普通函数和类方法。
(1) 装饰器定义__call__()方法;
(2) 装饰器定义__get__()方法,接收描述符类实例和主体类实例,返回可调用实例,此可调用实例的构造函数接收描述符类实例和主体类实例;
(3) 返回的可调用实例进行小括号运算时,进行描述符类实例的小括号运算,调用描述符类的call方法,将主体类实例和主体类实例原来的类方法的入参一并传递到*args。
示例
>>> class CountTrace:
def __init__(self,func):
self.calls=0
self.func=func
#实例名()运算,自动调用__call__
def __call__(self,*args,**kargs):
print('self1=',self)
print('*args=',args)
self.calls+=1
print('调用 {} {} 次'.format(self.func.__name__,self.calls))
return self.func(*args,**kargs)
def __get__(self,instance,owner):
print('self2=',self)
print('instance=',instance)
print('owner=',owner)
# 返回可调用对象,否则无法进行小括号运算
return wrapper(self,instance)
>>> class wrapper:
def __init__(self,desc,subj):
self.desc=desc
self.subj=subj
# 定义小括号运算
def __call__(self,*args,**kargs):
# 实例小括号运算,desc()调用 CountTrace 的 __call__
return self.desc(self.subj,*args,**kargs)
>>> @CountTrace
def testsquare(x):
print(x**2)
>>> class StaffWithDecorator:
def __init__(self,name,pay):
self.name=name
self.pay=pay
@CountTrace
def payraise(self,rate):
print('self3=',self)
self.pay*=(1.0+rate)
>>> testsquare(3)
self1= <__main__.CountTrace object at 0x0398D570>
*args= (3,)
调用 testsquare 1 次
9
>>> s1=StaffWithDecorator('张三',51000)
>>> s1.payraise(0.2)
self2= <__main__.CountTrace object at 0x0398D7D0>
instance= <__main__.StaffWithDecorator object at 0x0398DED0>
owner= <class '__main__.StaffWithDecorator'>
self1= <__main__.CountTrace object at 0x0398D7D0>
*args= (<__main__.StaffWithDecorator object at 0x0398DED0>, 0.2)
调用 payraise 1 次
self3= <__main__.StaffWithDecorator object at 0x0398DED0>