zoukankan      html  css  js  c++  java
  • 11.python描述器---类的装饰器

    描述器
    1.描述器定义

    (1)描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()这三个内置方法中的一个,描述符也被称为描述符协议
    (2)描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的属性,不能定义到构造函数)
    (3)如果只实现了__get__,就是非数据描述符non-data descriptor
    (4)同时实现了__get__,__set__就是数据描述符data descriptor
    (5)如果一个类的类属性设置为描述器,那么它被称为owner属性
    2.描述器用到的3个魔术方法:__get__(),__set__(),__delete__()
    (1)方法使用格式:
    ①object.__get__(self,instance,owner)
    ②object.__set__(self,instance,value)
    ③object.__delete__(self,instance)
    #self:指代当前实例本身,调用者
    #instance:是owner(属主)类的实例
    #owner:是属性的所属的类(好比我实例化了,我作为别人的属性,别人就是它的属主)
    (2)__get__()方法:当一个B类的类属性等于另一个A类的实例,且另一个A类又实现了描述器三方法之一的话,它就是描述器。如果通过B类属性访问,就会触发__get__()方法。如果通过B类实例属性访问A类不会触发__get__()方法

    #描述器类
    class A:
        def __init__(self,value='abc'):   #默认参数abc
            print('A.init')
            self.a1 = value
    
        #当一个类的类属性等于了另一个类实例的时候,且这个类实现了__get__(),__set__(),__delete__()三个其中一个,如果通过类属性访问,就会触发__get__()方法
        def __get__(self, instance, owner):
            print('调用get:','A实例:',self,'参数instance:',instance,'描述器被谁用:',owner)
            #如果没有return会抛出错误:AttributeError: 'NoneType' object has no attribute 'a1'
            return self
    
    class B:
        #B类属性放一个对象A的实例赋值x,
        #A实例又是描述器,放在了B类属性里,如果访问的话可以做拦截
        x = A()
    
        def __init__(self):
            print('B.init')
            #不会触发描述器机制,属性调用A类实例化__init__方法,做初始化的时候把父类的属性放在自己实例字典里
            self. x= A(123)              #实例化A传入参数123
    
    ########################################################################################
    #第一步:x = A()为B类属性,在B类定义的时候就已经存在,这个东西写上,解释器要替你解释,把B类一扫描完,A()调用A类实例化打印:A.init
    print('-'*50)
    #当B类属性x刚好是另一个A类的实例,而恰好这个A类又实现了描述器三方法之一的话,它就是描述器,如果是__get__()方法触发,就会拿到自己(A类)当前实例,拿到这个描述器的属主相关的B类型信息和属主B类相关的实例
    #打印B类字典
    print(B.__dict__)
    ########################################################################################
    print('+'*100)
    #第二步:B类通过类属性x就会触发A类(描述器)里的__get__方法,会返回A的实例
    print('B类触发A类里的__get__方法:')
    print(B.x)
    print('-'*30)
    print('B类触发A类里的__get__方法:')
    #第三步:明确的指出去B类的字典去找触发A类里的__get__方法,会返回A的实例,调用A实例访问a1属性
    print(B.x.a1)
    ########################################################################################
    print('+'*100)
    #第四步:B类实例化打印:B.init,紧接着调用到A实例打印:A.init
    b = B()
    #如果B类通过实例的属性self. x访问另一个类A,就不会执行A类里的__get__方法
    print('-'*30)
    print('B调用自己的类获取A实例和A实例的a1属性:')
    #第五步:搜索b实例自己的字典调用A实例
    print(b.x)
    #搜索b实例的自己的字典A实例的a1属性
    print(b.x.a1)
    ########################################################################################
    print('+'*100)
    #b实例的字典
    print(b.__dict__)
    
    ###返回结果:
    A.init
    --------------------------------------------------
    {'__weakref__': <attribute '__weakref__' of 'B' objects>, 'x': <__main__.A object at 0x0000000000D3A5F8>, '__module__': '__main__', '__doc__': None, '__dict__': <attribute '__dict__' of 'B' objects>, '__init__': <function B.__init__ at 0x0000000000DA8AE8>}
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    B类触发A类里的__get__方法:
    调用get: A实例: <__main__.A object at 0x0000000000D3A5F8> 参数instance: None 描述器被谁用: <class '__main__.B'>
    <__main__.A object at 0x0000000000D3A5F8>
    ------------------------------
    B类触发A类里的__get__方法:
    调用get: A实例: <__main__.A object at 0x0000000000D3A5F8> 参数instance: None 描述器被谁用: <class '__main__.B'>
    abc
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    B.init
    A.init
    ------------------------------
    B调用自己的类获取A实例和A实例的a1属性:
    <__main__.A object at 0x0000000000DB2898>
    123
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    {'x': <__main__.A object at 0x0000000000DB2898>}

    总结:上例子中只实现了__get__()方法,属于非数据描述符
    (3):__set__()为一个属性赋值时触发

    class A:
        def __init__(self,value='abc'):   #默认参数abc
            print('A.init')
            self.a1 = value
    
        def __get__(self, instance, owner):
            print('调用get:','A实例:',self,'属主类的实例:',instance,'描述器被谁用:',owner)
            #如果没有return会抛出错误:AttributeError: 'NoneType' object has no attribute 'a1'
            return self
    
        def __set__(self, instance, value):
            print('调用set:', 'A实例:', self, '属主类的实例:', instance, '实例属性:', value)
    
    class B:
        #会触发到__get__()方法
        x = A()
    
        def __init__(self):
            print('B.init')
            #实例中设置实例属性的时候触发描述器的__set__方法后,不给b实例字典里加东西
            self.x = 100                #相当于x=100
            #实例中设置实例属性的时候触发描述器的__set__方法,不给b实例字典里加东西
            self.x = A(123)             #如果调用到set方法实例化A传入参数123是传不进去的
    
    ########################################################################################
    #第一步:x = A()为B类属性,在B类定义的时候就已经存在,这个东西写上,解释器要替你解释,把B类一扫描完,A()调用A类实例化打印:A.init
    #######################################################################################
    print('+'*100)
    #第二步:
    #明确指出在B类的字典里去找,B类通过类属性x就会触发A类(描述器)里的__get__方法,会返回A的实例,通过A的实例访问a1
    print(B.x.a1)
    #######################################################################################
    print('+'*100)
    #第三步:
    b = B()
    #①B类实例化执行初始化方法优先打印:B.init
    #②B类实例中的self.x = 100方法触发了set方法,相当于操作类属性x,操作它的时候就把当前b=B()实例和100一起送到到set方法里,往B类字典里加入 'x': <>
    #③B类属性self.x = A(123)方法里的A(123)调用到A实例打印:A.init
    #④B类属性self.x = A(123)方法第二次触发set方法,相当于操作类属性x,操作它的时候就把当前b=B()实例和第二个实例A(123)一起送到set方法里,往B类字典里加入 'x': <>
    ######################################################################################
    print('+'*100)
    #B类的字典
    print(B.__dict__)
    #b实例的字典空,因为self.x = 100和self.x = A(123)属性都触发了set方法,就不会给实例字典里加东西
    print(b.__dict__)
    ######################################################################################
    print('+'*100)
    #第四步:
    #B类的类属性是一个描述器的话,它是一个数据描述器的话,你对同样名字的实例的操作相当于操作B类属性
    print(b.x.a1)
    ######################################################################################
    print('+'*100)
    #第五步:类没有用到描述器,赋值即定义
    B.x = 500
    print(B.x)
    #第六步:实例调用数据描述器的set方法,依然操作类属性,就会受描述器的控制
    b.x = 10000
    print(b.x)
    
    ###返回结果:
    A.init
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    调用get: A实例: <__main__.A object at 0x0000000000D2A5C0> 属主类的实例: None 描述器被谁用: <class '__main__.B'>
    abc
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    B.init
    调用set: A实例: <__main__.A object at 0x0000000000D2A5C0> 属主类的实例: <__main__.B object at 0x0000000000DA2518> 实例属性: 100
    A.init
    调用set: A实例: <__main__.A object at 0x0000000000D2A5C0> 属主类的实例: <__main__.B object at 0x0000000000DA2518> 实例属性: <__main__.A object at 0x0000000000DA2898>
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    {'__init__': <function B.__init__ at 0x0000000000D98B70>, '__dict__': <attribute '__dict__' of 'B' objects>, '__module__': '__main__', '__doc__': None, '__weakref__': <attribute '__weakref__' of 'B' objects>, 'x': <__main__.A object at 0x0000000000D2A5C0>}
    {}
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    调用get: A实例: <__main__.A object at 0x0000000000D2A5C0> 属主类的实例: <__main__.B object at 0x0000000000DA2518> 描述器被谁用: <class '__main__.B'>
    abc
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    500
    1000

    总结:上列子中同时实现了__get__,__set__就是数据描述符
    (4):__delete__()采用del删除属性时触发

    class Foo:                                     #定义新式类
        def __get__(self, instance, owner):        #定义__get__方法:一个对象调用属性的时候会触发
            print('===>get方法')
    
        def __set__(self, instance, value):        #定义__set__方法
            print('===>set方法')
    
        def __delete__(self, instance):            #定义__delete__方法
            print('===>delete方法')
    
    #描述符必须要有另外一个类属性里定义才会触发
    class Bar:                                    #定义一个Bar类
        x=Foo()                                   #在Bar类中定义一个类属性x,这个类属性的值是Foo实例化的结果就是描述符类的一个对象
    
    #被描述的类在操作属性的时候会触发get,set,delete三个方法
    b1=Bar()                                      #b1通过Bar得到一个实例
    #调用
    b1.x                                          #b1点x调用x这个属性就是Foo就可以触发描述符里的__get__方法:===>get方法
    #赋值
    b1.x=1                                        #b1点x=1调用x=1这个属性就是Foo就可以触发描述符里赋值的__set__方法:'===>set方法
    #删除
    del b1.x                                      #del b1点x调用x这个属性就是Foo就可以触发描述符里赋值的__delete__方法:'===>delete方法
    
    ####返回结果:
    ===>get方法
    ===>set方法
    ===>delete方法

    3.描述符的执行步骤(利用描述符修改实例化传的值)

    class Foo:                                     #定义描述符Foo用来代理另外一个Bar类
        def __set__(self, instance, value):        #第三步:instance传的是Bar的对象是b1,value是参数10
            print('===>set方法',instance,value)     #打印instance和value的值
            instance.__dict__['x']=value           #第四步把x的值修改成77777:instance.__dict__操作的是b1.__dict__,设置修改x的值,操作实例下的属性字典进行了一个真正的赋值操作
    
    class Bar:            #Bar类里定义了一个描述符Foo,这个Foo就是上面的类,定义它以后以为在Bar这个类产生的实例x属性的操作全部去找Foo描述符里的__set__方法
        x=Foo()           #在Bar这个类中,我用Foo去描述Bar类当中的x属性,意味着x属性被Foo代理了  定义类属性值是Foo()实例化的结果的找到描述符里的 __set__方法
        def __init__(self,n):
            self.x=n      #第二步:b1.x=10,x这个属性被代理了,赋值触发的是x=Foo()下的__set__方法
    
    b1=Bar(10)            #第一步:b1=Bar(10)触发__set__方法
    print(b1.__dict__)
    
    #修改x的值
    b1.x=7777777          #x=7777777触发的是__set__方法
    print(b1.__dict__)
    
    ####返回结果:
    ===>set方法 <__main__.Bar object at 0x005747B0> 10
    {'x': 10}
    ===>set方法 <__main__.Bar object at 0x005747B0> 7777777
    {'x': 7777777}

    4.描述符的应用
    举例1:基于描述符功能为python加上类型检测

    #创建描述符Typed
    class Typed:
        #创造构造函数init方法接收俩个参数:1.self,key和2.期望的expected_type
        def __init__(self,key,expected_type):            #第四步:__init__方法接收俩个参数:1.self,key和2.期望的expected_type
            self.key=key                                 #接收的key:name
            self.expected_type=expected_type             #接收期望的类型expected_type:str
    
        #定义__get__方法
        def __get__(self, instance, owner):     #instance是p1实例本身,owner是p1的类
            print('get方法')
            return instance.__dict__[self.key]  #把instance存到属性字典里,触发get方法得到返回值,
    
        #定义__set__方法(因为实例属性高于非数据描述符所以必须定义成数据描述符)
        def __set__(self, instance, value):               #第三步:set方法会接收俩个参数,第一个参数就是p1实例本身instance,第二个参数就是赋的值value
            print('set方法')
    
            if not isinstance(value,self.expected_type):  #第五步:存到instance的字典里之前判断value的类型如果不是expected_type接收到期望的类型的时候
                raise TypeError('%s 传入的类型不是%s' %(self.key,self.expected_type))   #报错
            instance.__dict__[self.key]=value             #执行__set__方法后代理在操作把传过来的值传到底层instance字典
    
    
    #定义People类
    class People:
        #给name用到描述符传俩个参数一个是name另一个是期望的类型str
        name=Typed('name',str)                         #第二步:.name会被数据描述符Typed代理,会触发set方法(把传的值name和期望的类型传给def __init__(self,key,expected_type):)
    
        #创造构造函数分别是名字年纪
        def __init__(self,name,age):
            self.name=name                             #这一步触发的是代理而不是实例
            self.age=age
    
    ####实例化触发set方法检测传入的值是否是str类型和int类型
    p1=People('xixi',18)                              #第一步:实例化触发self.name的操作会触发set方法
    #不是str类型报错
    #p1=People(77,18)                                 #TypeError: name 传入的类型不是<class 'str'>
    #不是int类型报错
    #p1=People('xixi','18')                           #TypeError: age 传入的类型不是<class 'int'>
    print('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
    ####查询p1.name直接找到找到get方法里的instance里的字典找到key
    print(p1.name)                                       #打印结果:xixi
    print('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
    ####修改name的名字
    print(p1.__dict__)                                   #查看修改前结果:{'name': 'xixi', 'age': 18}
    #调用set方法代理操作把传过来的值传到底层instance字典
    p1.name='YAOYAO'                                   #调用set方法代理操作把传过来的值传到底层instance字典完成修改
    print(p1.__dict__)                                   #打印修改结果
    
    ####打印结果:
    set方法
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    get方法
    xixi
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    {'name': 'xixi', 'age': 18}
    set方法

    举例2:对实例的数据进行校验

    class Person:
        def __init__(self,name:str,age:int):
            self.name = name
            self.age = age

    对上面的类的实例的属性name,age进行数据类型校验
    方式一:描述器方式(需要使用数据描述器,写入实例属性的时候做检查)

    #判断类型描述器类
    class Typed:
        #type接收name和age的类型
        def __init__(self,type):
            self.type = type
    
        def __get__(self, instance, owner):
            pass
    
        def __set__(self,instance,value):
            print('调用set:', '实例:', self, '参数instance:', instance, '实例属性:', value)
            #isinstance函数判断一个对象是否是一个已知的类型(value是判断的对象,self.type是判断的类型)
            if not isinstance(value,self.type):
                #如果类型不对抛出异常把判断的对象扔出去
                raise ValueError(value)
    
    class Person:
        #Typed实例1:name
        name = Typed(str)
        #Typed实例2:age
        age = Typed(int)
    
        def __init__(self,name:str,age:int):
            #实例中设置实例属性的时候触发__set__方法,把实例属性name传进去
            self.name = name
            #实例中设置实例属性的时候触发__set__方法,把实例属性age传进去
            self.age = age
    
    #实例化把年龄类型写成字符类型判断
    p1 = Person('xixi','18')                #返回结果:ValueError: 18

    总结:代码看似不错,但是有硬编码
    方式二:使用inspect模块直接获取形参类型

    #参数类型判断描述器
    class Typed:
        #type接收name和age的类型
        def __init__(self,type):
            self.type = type
    
        def __get__(self, instance, owner):
            pass
    
        def __set__(self,instance,value):
            print('调用set:', '实例:', self, '参数instance:', instance, '实例属性:', value)
            #isinstance函数判断一个对象是否是一个已知的类型(value是判断的对象,self.type是判断的类型)
            if not isinstance(value,self.type):
                #如果类型不对抛出异常把判断的对象扔出去
                raise ValueError(value)
    
    #函数装饰器
    def TypeAssert(cls):
            import inspect
            #参数放到有序的字典中
            params = inspect.signature(cls).parameters
            #print(params)
            #遍历这个字典
            for name, param in params.items():
                #annotation返回类型
                #print(param.name, param.annotation)
                #判断遍历的值是否有注解,有注解的检查
                if param.annotation != param.empty:
                    #有注解不等于空的动态加了一个类属性,这个类属性指向了Typed描述器
                    #内建函数setattr,给类Person属性加上name = Typed(str)和age = Typed(int)
                    setattr(cls, name, Typed(param.annotation))
            return cls
    
    @TypeAssert
    class Person:
        def __init__(self,name:str,age:int):
            self.name = name
            self.age = age
    
    p1 = Person('xixi','18')            #返回结果:ValueError: 18

    二.python中的描述器:在Python中应用非常广泛
    1.描述器是可以实现大部分python类特性中的底层魔法,包括@classmethod,@staticmethd,@property甚至是__slots__属性。描述器是很多高级库和框架的重要工具质疑,描述器通常是使用到装饰器或元类的大型框架中的一个组件
    2.非数据描述器:Python的方法(包括静态方法staticmethod()和类方法classmethod())都实现为非数据描述器。因此,实例可以重新定义和覆盖方法。这允许单个实例获取与同一类的其他实例不同的行为。
    (1)实现StaticMethod装饰器,完成StaticMethod装饰器的功能

    #类装饰器
    class StaticMethod:
        def __init__(self,fn):                   #fn参数接收传的函数foo
            #打印接收的foo函数
            print(fn)
            self.fn = fn
    
        def __get__(self, instance, owner):
            print('调用get:', 'StaticMethod类实例:', self, '参数instance:', instance, '描述器被谁用:', owner)
            #返回foo函数
            return self.fn
    
    class A:
        @StaticMethod
        def foo():                               #foo = StaticMethod(foo)
            print('静态的')
    
    #A.foo去StaticMethod类里触发的__get__方法,最后return返回原来的foo函数,原来的foo函数在self.fn,赋值给f
    f = A.foo
    #打印foo函数
    print(f)
    #foo函数加()直接调用
    f()
    ##############返回结果:
    <function A.foo at 0x0000000000D78AE8>
    调用get: StaticMethod类实例: <__main__.StaticMethod object at 0x00000000006BADA0> 参数instance: None 描述器被谁用: <class '__main__.A'>
    <function A.foo at 0x0000000000D78AE8>
    静态的

    (2)实现ClassMethod装饰器,完成ClassMethod装饰器的功能

    from functools import partial               #partial返回函数
    
    #类装饰器
    class ClassMethod:
        def __init__(self,fn):                   #fn参数接收传的函数bar
            #打印接收的bar函数
            print(fn)
            self.fn = fn
    
        def __get__(self, instance, owner):
            print('调用get:', 'StaticMethod类实例:', self, '参数instance:', instance, '描述器被谁用:', owner)
            #通过partial返回第一参数bar函数和属主A类
            return partial(self.fn,owner)
    
    class A:
        @ClassMethod
        def bar(cls):
            #获取A类的名字
            print(cls.__name__)
    
    #A.bar去ClassMethod类里触发的__get__方法,最后return返回bar函数和属主A类,原来的bar函数在self.fn,属主A类在owner里,赋值给f
    f = A.bar
    #打印bar函数属主A类
    print(f)
    #bar函数加()获取A类的名字
    f()
    ##############返回结果:
    <function A.bar at 0x0000000000AA8AE8>
    调用get: StaticMethod类实例: <__main__.ClassMethod object at 0x0000000000DC2828> 参数instance: None 描述器被谁用: <class '__main__.A'>
    functools.partial(<function A.bar at 0x0000000000AA8AE8>, <class '__main__.A'>)
    A

    3.property函数实现为一个数据描述器
    (1)静态属性@property方式实现装饰器函数封装

    class Room:                                  #定义Room类
        def __init__(self,name,width,length):    #定义初始化函数名字,宽度,长度
            self.name=name
            self.width=width
            self.length=length
    
        @property                                #静态属性:实现方法是property(area)给传过去运行结果在给area,相当于给Room类属性加了一个#area=property(area)
        def area(self):
            return  self.width * self.length    #面积公式
    
    r1=Room('卧室',20,20)                        #实例化出一个房间求面积
    print(r1.area)                               #调的是属性area,实际触发的是area(self)方法的运行,因为@property把方法伪装成属性。
    
    ####打印结果:
    400

    (2)通过描述符实现自定制property

    class Lazypropery:                        #定义一个类Lazyproperty
        def __init__(self,func):
            self.func=func                     #第二步:触发def __init__(self,func):方法后给func设一个值进去
    
        #Lazypropery类里加上__get__方法把Lazyproperty做成描述符
        def __get__(self, instance, owner):    #第六步:触发__get__方法
            #实例调用
            #print('get')     #实例调用触发get方法
            #print(instance)  #实例调用instance是r1实例本身:<__main__.Room object at 0x002A66B0>
            #print(owner)     #实例调用owner是r1的类:<class '__main__.Room'>
            #类调用
            #print('get')     #类调用触发get方法
            #print(instance)  #类调用instance没有实例返回:None
            #print(owner)     #类调用owner是r1的类:<class '__main__.Room'>
    
            # 第七步:在__get__方法里运行函数传给def area(self):里的self
            if instance is None:               #做个判断如果instance如果是None
                return self                    #返回Lazypropery(area)的实例
            res = self.func(instance)           #如果有参数接收func加自己的参数instance返回res
            return res                         #return res传给def area(self):里的self
    
    
    class Room:                                 #定义Room类
        def __init__(self,name,width,length):   #定义初始化构造函数名字,宽度,长度
            self.name=name
            self.width=width
            self.length=length
    
        #@Lazyproperty语法糖会把Lazypropery加一个括号运行,括号里必须传一个参数,参数就是def area(self):里的名字area,变成Lazypropery(area),最后把这个实例赋值给arear,意味着Room类里面的属性字典area是key,vale变成Lazyproperty的一个对象,即area=Lazypropery(area)
        #@Lazyproperty就是在为Room类增加一个描述符的操作类属性,最终效果把area这个属性代理给了描述符Lazypropery(area)这个对象,现在Room类当中的arear这个属性被别人代理了
        @Lazypropery           #第一步:运行Lazypropery(area)实例化会触发Lazyproperty类下面的 __init__方法
        def area(self):
            return  self.width * self.length    #面积公式
    
    r1=Room('卧室',20,20)     #第三步:实例化
    #实例调用
    print(r1.area)             #第四步:调用r1.area属性会触发非数据描述符,r1这个字典里没有area这个属性。找不到从类Room.__dict__里的找area,发现arear被非数据描述符Lazypropery被代理了,于是会触发描述符代理的get方法
    ####
    print('~~~~~~~~~~~')
    #类调用
    print(Room.area)
    
    ####返回结果:
    400
    ~~~~~~~~~~~
    <__main__.Lazypropery object at 0x01D56650>

    (3)利用描述符原理完成一个自定制@property做延迟计算功能,第一次触发要计算,计算好在一个地方放起来,第二次触发去放好的地方拿过来(本质就是把一个函数属性利用装饰器原理做成一个描述符:类的属性字典中函数名为key,value为描述符类产生的对象)

    class Lazypropery:                        #定义一个类Lazyproperty
        def __init__(self,func):
            self.func=func                     #第二步:触发def __init__(self,func):方法后给func设一个值进去
    
        #Lazypropery类里加上__get__方法把Lazyproperty做成描述符
        def __get__(self, instance, owner):    #第六步:触发__get__方法
            print('get')  # 实例调用触发get方法
            # 第七步:在__get__方法里运行函数传给def area(self):里的self
            if instance is None:               #做个判断如果instance如果是None
                return self                    #返回Lazypropery(area)的实例
            res = self.func(instance)           #如果有参数接收func加自己的参数instance返回res
            setattr(instance, self.func.__name__, res)   #第八步:instance实例,self.func.__name__是key,vale是res把计算的结果放到实例的属性字典里
            return res                         #return res传给def area(self):里的self
    
    
    class Room:                                 #定义Room类
        def __init__(self,name,width,length):   #定义初始化构造函数名字,宽度,长度
            self.name=name
            self.width=width
            self.length=length
    
        #@Lazyproperty语法糖会把Lazypropery加一个括号运行,括号里必须传一个参数,参数就是def area(self):里的名字area,变成Lazypropery(area),最后把这个实例赋值给arear,意味着Room类里面的属性字典area是key,vale变成Lazyproperty的一个对象,即area=Lazypropery(area)
        #@Lazyproperty就是在为Room类增加一个描述符的操作类属性,最终效果把area这个属性代理给了描述符Lazypropery(area)这个对象,现在Room类当中的arear这个属性被别人代理了
        @Lazypropery           #第一步:运行Lazypropery(area)实例化会触发Lazyproperty类下面的 __init__方法
        def area(self):
            return  self.width * self.length    #面积公式
    
    
    r1=Room('卧室',20,20)     #第三步:实例化
    #实例第一次调用
    print(r1.area)             #第四步:调用r1.area属性会触发非数据描述符,r1这个字典里没有area这个属性。找不到从类Room.__dict__里的找area,发现arear被非数据描述符Lazypropery被代理了,于是会触发描述符代理的get方法
    #
    print('~~~~')
    #第九步:第二次调用从实例的字典里要不会运行func函数不会调用到__get方法__
    print(r1.area)
    
    ##返回结果:
    get
    400
    ~~~~
    400

    (4)@property的补充功能
    方式一:

    class Foo:
        @property
        def XIXI(self):
            print('get的时候运行')
    
        #设值需要再定义一个函数这个函数前面加一个装饰器叫@XIXI.setter
        @XIXI.setter           #用来定义设置XIXI属性的时候会触发def XIXI(self):下的方法
        def XIXI(self,val):    #把f1传给self,把xi传给val
            print('set的时候运行',val)
    
        #删除需要再定义一个函数这个函数前面加一个装饰器叫@XIXI.deleter
        @XIXI.deleter
        def XIXI(self):
            print('del的时候运行')
    
    f1=Foo()
    #调用
    f1.XIXI         #调用XIXI必须要有@property
    
    #赋值会触发@XIXI.setter的运行
    f1.XIXI='xi'  #只定义静态属性是不能给XIXI设值必须@XIXI.setter
    
    #删除的时候触发@XIXI.deleter的运行
    del f1.XIXI
    
    ####返回结果:
    get的时候运行
    set的时候运行 xi
    del的时候运行

    另一种写法:利用property内置函数的方式去写

    class Foo:
        def get_XIXI(self):
            print('get的时候运行')
    
        def set_XIXI(self,val):    #把f1传给self,把xi传给val
            print('set的时候运行',val)
    
        def del_XIXI(self):
            print('del的时候运行')
    
        #利用property内置函数的方式去写
        XIXI=property(get_XIXI,set_XIXI,del_XIXI)  #只有在属性XIXI定义property后才能定义XIXI.setter,XIXI.deleter,顺序必须是获取设置删除
    
    f1=Foo()
    #获取
    f1.XIXI
    #赋值
    f1.XIXI='xi'
    #删除
    del f1.XIXI
    
    ###返回结果:
    get的时候运行
    set的时候运行 xi
    del的时候运行

    6.@property具体用法

    class Goods:                            #定义商品的类Goods
        def __init__(self):
            #Goods类里封装了原价
            self.original_price = 50
            #Goods类里封装了折扣
            self.discount = 0.9
    
        #定义了静态属性
        @property                    #第三步:执行def price(self):
        def price(self):
            #实际价格 = 原价 * 折扣
            new_price = self.original_price * self.discount  #第四步:执行50*0.9   第八步:执行100*0.9
            return new_price
    
        #一有人改价格会触发def price(self, value):函数的运行
        @price.setter
        def price(self, value):
            self.original_price = value    #第六步:会把修改的价格100赋值给original_price改掉折扣不变
    
        @price.deleter
        def price(self):
            del self.original_price        #第十步:删除最原始的价格original_price
    
    
    obj = Goods()           #第一步:实例化
    #获取
    print(obj.price)        #第二步:获取商品原始价格触发@property
    #设置
    obj.price = 100         #第五步:设置价钱触发 @price.setter
    #获取修改后
    print(obj.price)        #第七步:获取修改后商品价格触发@property
    #删除
    del obj.price           #第九步:删除商品原价
    #获取删除后
    #print(obj.price)       #第十一步:获取删除后商品价格触发@property会报错因为没有这个属性了
    
    ###返回结果:
    45.0
    90.0

    类的装饰器
    1.回顾函数调用装饰器
    @语法糖的工作原理是装饰器函数括号把对象的名字放进来,对于函数调用装饰器(test)是函数的内存地址

    #定义一个装饰器函数deco
    def deco(func):
         print('==========')
         return func
    
    #函数调用装饰器
    #@deco做的事相当于test=deco(test),test做的事就是deco(test)运行了
    @deco       #test=deco(test):deco括号把test传给def deco(func):进去得到一个返回值return func赋值给test
    def test():
         print('test函数运行')
    test()                          #test()运行得到:test函数运行
    
    ###返回结果:
    ==========

    2.类调用装饰器工作的基本原理

    #第一步:定义一个装饰器函数deco
    def deco(obj):  #第三步:类名字Foo传过来以后
         obj.x=1     #利用类把属性加到Foo的属性字典里去了
         obj.y=2     #利用类把属性加到Foo的属性字典里去了
         return obj #返回类本身
    
    #类调用装饰器
    #@deco相当于运行Foo=deco(Foo),把Foo传给obj了,obj就是Foo这个类
    @deco         #第二步:利用语法糖的形式,deco括号把类的名字Foo传进来,赋值给Foo,把类名字传到def deco(obj):的obj里
    class Foo:   #语法糖修饰了一个类Foo
        pass
    f1=Foo()      #f1用Foo实例化
    print(f1)
    print(Foo.__dict__)   #现在查看的是被deco处理过的Foo了
    
    ####返回结果:
    <__main__.Foo object at 0x02070550>
    {'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None, 'x': 1, 'y': 2}

    3.类调用装饰器修改版可以传多个类

    def Typed(**kwargs):                #第二步:Typed外层会接收kwargs等于是一个字典{'x': 1, 'y': 2}
        def deco(obj):                  #第五步:运行deco局部作用域里面外层传了一个字典进来,这个obj就是类Foo
            #print('deco里的obj属性',kwargs)     #打印deco里obj的属性属性字典{'x': 1, 'y': 2}
            #print('类名', obj)                  #打印类名
            for key, val in kwargs.items():      #第六步:items把字典里{'x': 1, 'y': 2}转换成得到元祖形式(('x',1),('y',2))做一个for循环把x给key,把y给val
                setattr(obj, key, val)           #设置进去obj类名,key,val,obj是People类,key是x,val是1
            return obj                  #第七步返回obj
        print('Typed里的',kwargs)       #第二步执行结果打印kwargs:{'x': 1, 'y': 2}
        return deco                     #第三步:返回值是deco
    
    #Foo类:传(x=1,y=2)
    @Typed(x=1,y=2)                     #第一步:@Typed函数名加上小括号(x=1,y=2)运行函数Typed,   第四步:得到返回值是deco跟@deco连到一起去用进行@语法糖操作括号把类的名字Foo传进来,赋值给Foo,把类名字传到def deco(obj):的obj里
    class Foo:   #语法糖修饰了一个类Foo
        pass
    f1=Foo()      #f1用Foo实例化
    print(Foo.__dict__)   #现在查看的是被deco处理过的Foo了
    
    print('~~~~~~~~~~~~~~~~~~~~~~')
    
    #Bar类:传(name='xixi')
    @Typed(name='xixi')    #相当于@deco把Bar=deco(Bar),原理同上面
    class Bar:
         pass
    print(Bar.name)         #打印是否修改成功
    
    ####返回结果:
    Typed里的 {'x': 1, 'y': 2}
    {'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None, 'x': 1, 'y': 2}
    ~~~~~~~~~~~~~~~~~~~~~~
    Typed里的 {'name': 'xixi'}
    xixi

    4.通过定义类装饰器结合描述符给类查看属性不限制重复代码

    class Typed:
        # 创造构造函数init方法接收俩个参数:1.self,key和2.期望的expected_type
        def __init__(self,key,expected_type):            #第四步:__init__方法接收俩个参数:1.self,key和2.期望的expected_type
            self.key=key                                 #接收的key:name
            self.expected_type=expected_type             #接收期望的类型expected_type:str
    
        # 定义__get__方法
        def __get__(self, instance, owner):  # instance是p1实例本身,owner是p1的类
            print('get方法')
            return instance.__dict__[self.key]  # 把instance存到属性字典里,触发get方法得到返回值,
    
        #定义__set__方法(因为实例属性高于非数据描述符所以必须定义成数据描述符)
        def __set__(self, instance, value):              #第三步:set方法会接收俩个参数,第一个参数就是p1实例本身instance,第二个参数就是赋的值value
            print('set方法')
    
            if not isinstance(value,self.expected_type): #第五步:存到instance的字典里之前判断value的类型如果不是expected_type接收到期望的类型的时候
                raise TypeError('%s 传入的类型不是%s' %(self.key,self.expected_type))   #报错
            instance.__dict__[self.key]=value             #执行__set__方法后代理在操作把传过来的值传到底层instance字典
    
    
    #定义装饰器给类增加类属性,这个类属性比较特殊是描述符
    def deco(**kwargs):                              #第二步:deco外层会接收kwargs等于是一个字典{'name':str,'age':int}
        def wrapper(obj):                            #第五步:运行wrapper局部作用域里面外层传了一个字典进来,这个obj拿到的就是People的类
            #print('wrapper里的obj属性',kwargs)       #打印wrapper里obj的属性属性字典
            for key, val in kwargs.items():          #第六步:items把字典{'name':str,'age':int}里key属性名和val类型变成元祖形式(('name',str),('age',int))做一个循环把name给key,把str给val
                #print(key,val)                       #打印key和val分别是name <class 'str'>和age <class 'int'>
    
                #setattr(People,'name',Typed('name',str)):obj是People类,key是name,val是Typed('name',str)相当于做了name=Typed('name',str)
                setattr(obj, key, Typed(key, val))    #第七步:给类key增加一个类属性,需要被描述符代理所以要产生一个描述符Typed做实例把key属性名和val想要的类型传进来
            return obj                               #第八步返回obj
        #print('deco里的',kwargs)                     #打印查看第二步执行结果是:kwargs:{'name':str,'age':int}
        return wrapper                               #第三步:直接返回值是wrapper
    
    #应用装饰器deco
    @deco(name=str,age=int)                            #第一步:定义name=str类型,age=int类型,deco()运行传给装饰器里的**kwargs   第四步:得到返回值是wrapper跟@wrapper连到一起去用进行@语法糖操作括号把类的名字People传进来,赋值给People,把类名字传到def  wrapper(obj):的obj里,即wrapper ===>People=wrapper(People)
    class People:
    
        # name=Typed('name',str)   如果定义类的装饰器后查看类型限制不用在这里加
        # age=Typed('age',int)     如果定义类的装饰器查看类型限制不用在这里加
    
        def __init__(self,name,age):
            self.name=name
            self.age=age
    
    p1=People('xixi',18)
    print(People.__dict__)
    
    ####返回结果:
    set方法
    set方法
    {'__module__': '__main__', '__init__': <function People.__init__ at 0x021D98E8>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None, 'name': <__main__.Typed object at 0x021D49D0>, 'age': <__main__.Typed object at 0x021D4A10>}
  • 相关阅读:
    AcWing 3302. 表达式求值
    AcWing 828. 模拟栈
    六种风格时间显示
    web2.0常用配色.
    CSS浏览器兼容问题详解
    jQuery Cycle Plugin Beginner Demos
    jQuery插件Clipboard Copy(复制)。
    精通jQuery选择器使用
    jQuery插件右下角弹出信息
    CSS关于box(盒模式)的一系列问题
  • 原文地址:https://www.cnblogs.com/xixi18/p/9837842.html
Copyright © 2011-2022 走看看