python可调用实例.嵌套函数.描述符装饰类方法

1 python可调用实例.嵌套函数.描述符装饰类方法

函数装饰器可以是一个普通函数,也可以是一个可调用类的实例(有__call__()方法的类,此类实例可以像调用函数一样进行小括号运算,自动调用__call__()方法,即有__call__()方法的类实例,称为可调用类的实例)。

函数装饰器可以装饰普通函数,也可以装饰类方法。

类方法被普通函数装饰时,python向self传递隐含的主体实例;

类方法被可调用类实例装饰时,python只传递这个类的实例,可通过显式传递被装饰的类实例来正确调用类方法。

1.1 可调用类实例装饰类方法

用法

>>> 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

1.2 嵌套函数装饰类方法

描述

使用嵌套函数装饰器,可以装饰普通函数和类方法,并且能正常工作,不需要额外显式送类实例。

嵌套函数装饰类方法时,自动将类实例传递给类方法的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 19
>>> 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

1.3 描述符装饰类方法

1.3.1 get和call

描述

在之前的描述符章节学习过,描述符是一个类,它有__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()
获取属性
可调用类实例

1.3.2 描述符装饰类方法

描述

描述符是一个有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 19
>>> 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>

你可能感兴趣的:(python,python)