• 面向对象进阶(三)----------描述符


    描述符(__get__, __set__, __delete__)

    1. 描述符是什么:

    描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议。

    __get__():调用一个属性时,触发
    __set__():为一个属性赋值时,触发
    __delete__():采用del删除属性时,触发

    class Foo: #在python3中Foo是新式类,它实现了三种方法,这个类就被称作一个描述符
        def __get__(self, instance, owner):
            pass
        def __set__(self, instance, value):
            pass
        def __delete__(self, instance):
            pass

     

    2. 描述符是干什么的:

    描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数__init__中)

    2.1 引子:描述符类产生的实例进行属性操作并不会触发三个方法的执行

    class Foo:
        def __get__(self, instance, owner):
            print('触发get')
        def __set__(self, instance, value):
            print('触发set')
        def __delete__(self, instance):
            print('触发delete')
    
    #包含这三个方法的新式类称为描述符,由这个类产生的实例进行属性的调用/赋值/删除,并不会触发这三个方法
    f1=Foo()
    f1.name = 'lebron'
    f1.name
    del f1.name
    
    #疑问:何时,何地,会触发这三个方法的执行

     

    2.2 描述符应用之何时?何地?

    #描述符Str
    class Str:
        def __get__(self, instance, owner):
            print('Str调用')
    
        def __set__(self, instance, value):
            print('Str设置...')
         # instance.__dict__[name] = value 加上这一行代码,则将会在被描述符代理的对象中的属性字典里面加上‘name’= value
    def __delete__(self, instance): print('Str删除...') #描述符Int class Int: def __get__(self, instance, owner): print('Int调用') def __set__(self, instance, value): print('Int设置...') def __delete__(self, instance): print('Int删除...') class People: name = Str() # name被Str类代理 age = Int() # age被Int类代理 def __init__(self, name, age): self.name = name self.age = age #何地?:定义成另外一个类的类属性 #何时?:且看下列演示 p1=People('alex',18) #描述符Str的使用 p1.name p1.name = 'egon' del p1.name #描述符Int的使用 p1.age p1.age = 18 del p1.age #我们来看到底发生了什么 print(p1.__dict__) print(People.__dict__) #补充 print(type(p1) == People) # type(obj)其实是查看obj是由哪个类实例化来的 print(type(p1).__dict__ == People.__dict__) >>> Str设置... Int设置... Str调用 Str设置... Str删除... Int调用 Int设置... Int删除... {} {'__module__': '__main__', 'name': <__main__.Str object at 0x00000190E6027748>, 'age': <__main__.Int object at 0x00000190E6027780>, '__init__': <function People.__init__ at 0x00000190E6042048>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None} True True

     

    3. 描述符分两种
    3.1 数据描述符:至少实现了__get__()和__set__()

    class Foo:
         def __set__(self, instance, value):
             print('set')
         def __get__(self, instance, owner):
             print('get')

     

    3.2 非数据描述符:没有实现__set__()

    class Foo:
         def __get__(self, instance, owner):
             print('get')

     

    4.注意事项

    一 描述符本身应该定义成新式类,被代理的类也应该是新式类
    二 必须把描述符定义成这个类的类属性,不能为定义到构造函数__init__中
    三 要严格遵循该优先级,优先级由高到底分别是

    1.类属性
    2.数据描述符
    3.实例属性
    4.非数据描述符
    5.找不到的属性触发__getattr__()

    类属性 > 数据描述符

    #描述符Str
    class Str:
        def __get__(self, instance, owner):
            print('Str调用')
        
        def __set__(self, instance, value):
            print('Str设置...')
        
        def __delete__(self, instance):
            print('Str删除...')
    
    class People:
        name = Str()
        def __init__(self, name, age):  # name被Str类代理,age被Int类代理,
            self.name = name
            self.age = age
    
    
    #基于上面的演示,我们已经知道,在一个类中定义描述符它就是一个类属性,存在于类的属性字典中,而不是实例的属性字典
    
    #那既然描述符被定义成了一个类属性,就可直接通过类名调用
    People.name  # 调用类属性name,本质就是在调用描述符Str,触发了__get__()
    
    People.name = 'egon'  # 那赋值呢,并没有触发__set__()
    del People.name  # del也没有触发__delete__()
    
    '''
    原因:描述符在使用时被定义成另外一个类的类属性,因而类属性比二次加工的描述符伪装而来的类属性有更高的优先级
    People.name #调用类属性name,找不到就去找描述符伪装的类属性name,触发了__get__()
    
    People.name='egon' #那赋值呢,直接赋值了一个类属性,它拥有更高的优先级,相当于覆盖了描述符,肯定不会触发描述符的__set__()
    del People.name #同上
    '''

     

    数据描述符 > 实例属性

    #描述符Str
    class Str:
        def __get__(self, instance, owner):
            print('Str调用')
        
        def __set__(self, instance, value):
            print('Str设置...')
        
        def __delete__(self, instance):
            print('Str删除...')
    
    class People:
        name = Str()
        def __init__(self, name, age): #name被Str类代理,age被Int类代理,
            self.name = name
            self.age = age
    
    
    p1 = People('egon', 18)  # 触发__set__
    
    #如果描述符是一个数据描述符(即有__get__又有__set__),那么p1.name的调用与赋值都是触发描述符的操作,于p1本身无关了,相当于覆盖了实例的属性
    p1.name = 'egonnnnnn'  # 触发__set__
    p1.name   # 触发__get__
    print(p1.__dict__)  # 实例的属性字典中没有name,因为name是一个数据描述符,优先级高于实例属性,查看/赋值/删除都是跟描述符有关,与实例无关了
    del p1.name   # 触发__delete__

     

    实例属性 > 非数据描述符

    #非数据描述符Str
    class Str:
        def __get__(self, instance, owner):
            print('Str调用')
    
    
    class People:
        name = Str()
    
        def __init__(self, name, age):  # name被Str类代理,age被Int类代理,
            self.name = name
            self.age = age
    
    
    p1 = People('chen', 18)  # 不会触发__set__
    
    # Str是一个非数据描述符,因为name=Str()而Str没有实现__set__方法,因而比实例属性有更低的优先级
    # 对实例的属性操作,触发的都是实例自己的
    
    p1.name   # 不会触发__get__
    print(p1.__dict__)  # {'name': 'chen', 'age': 18}
    del p1.name   # 不会触发__delete__

     

    5 描述符使用

    众所周知,python是弱类型语言,即参数的赋值没有类型限制,下面通过描述符机制来实现类型限制功能

    class Typed:
        def __init__(self, key, type):
            self.key = key
            self.type = type
    
        def __get__(self, instance, owner):
            if not instance:
                return self             #类调用方法时,传入的instance为空
            return instance.__dict__[self.key]
    
        def __set__(self, instance, value):
            if not isinstance(value, self.type):
                raise TypeError('传入的不是%s' % self.type)
            instance.__dict__[self.key] = value
    
        def __delete__(self, instance):
            instance.__dict__.pop(self.key)
    
    
    class People:
        name = Typed('name', str)
        age = Typed('age', int)
        salary = Typed('salary', float)
    
        def __init__(self, name, age, salary):
            self.name = name
            self.age = age
            self.salary = salary
    
    
    p1 = People('chen_jian', 25, 250000.1)
    p2 = People('CHEN', 22, 14000.1)
    
    print(p1.__dict__)
    p1.name = 'CHENJINA'
    print(p1.__dict__)
    
    >>>
    {'name': 'chen_jian', 'age': 25, 'salary': 250000.1}
    {'name': 'CHENJINA', 'age': 25, 'salary': 250000.1}

    6. 类的装饰器

    上述案例通过描述符已经能实现功能了,但是问题是,如果类有很多属性,仍然采用在定义一堆类属性的方式去实现,就会有很多重复代码,可以利用类的装饰器来解决

    #函数的装饰器
    def deco(func):
        print('函数的装饰器--------->')
        return func
    
    @deco   #等同于Foo = deco(test)
    def test():
        pass
    
    
    #类似地有类的装饰器
    
    def deco1(obj):
        print('类的装饰器---------->')
        obj.x = 23
        obj.y = 6
        obj.z = 23
        return obj
    
    @deco1   #等同于Foo = deco1(Foo)
    class Foo:
        pass
    
    print(Foo.__dict__)
    
    >>>
    函数的装饰器--------->
    类的装饰器---------->
    {'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None, 'x': 23, 'y': 6, 'z': 23}

     

    类的有参装饰器

    def typed(**kwargs):
    
        def deco(obj):
            for key, value in kwargs.items():
                #obj.__dict__[key] = value  未知错误
                setattr(obj, key, value)
            return obj
    
        return deco
    
    @typed(x=23, y=6, z=24 )
    class Foo:
        pass
    
    print(Foo.__dict__)
    
    @typed(name = 'lebron_james')
    class Name:
        pass
    
    print(Name.__dict__)
    
    >>>
    {'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None, 'x': 23, 'y': 6, 'z': 24}
    {'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Name' objects>, '__weakref__': <attribute '__weakref__' of 'Name' objects>, '__doc__': None, 'name': 'lebron_james'}

    参数赋值类型限制的终极版

    class Typed:
        def __init__(self, key, type):
            self.key = key
            self.type = type
    
        def __get__(self, instance, owner):
            if not instance:
                return self
            return instance.__dict__[self.key]
    
        def __set__(self, instance, value):
            if not isinstance(value, self.type):
                raise TypeError('传入的不是%s' % self.type)
            instance.__dict__[self.key] = value
    
        def __delete__(self, instance):
            instance.__dict__.pop(self.key)
    
    def deco(**kwargs):  #kwarge={'name'=str,'age'=int,'salary'=float}
    
        def add(obj):
            for key, value in kwargs.items():  #kwargs.items=(('name','str'),('age','int'),('salary','float''))
                setattr(obj, key, Typed(key, value))  #等同于setattr(People, name/*/*, Typed('name', str))
            return obj
        return add
    
    @deco(name = str, age = int, salary = float)  #等同于先运行deco(),再@add --->People=add(People)
    class People:
        # name = Typed('name', str)
        # age = Typed('age', int)
        # salary = Typed('salary', float)
    
        def __init__(self, name, age, salary):
            self.name = name
            self.age = age
            self.salary = salary
    
    
    p1 = People('chen_jian', 25, 250000.1)
    p2 = People('CHEN', 22, 14000.1)
    print(p1.__dict__)
    print(p2.__dict__)
    
    >>>
    {'name': 'chen_jian', 'age': 25, 'salary': 250000.1}
    {'name': 'CHEN', 'age': 22, 'salary': 14000.1}

    6 描述符总结

    描述符是可以实现大部分python类特性中的底层魔法,包括@classmethod, @staticmethd, @property甚至是__slots__属性

    描述符是很多高级库和框架的重要工具之一, 描述符通常是使用到装饰器或者元类的大型框架中的一个组件.

    7 利用描述符原理完成一个自定制@property,实现延迟计算(本质就是把一个函数属性利用装饰器原理做成一个描述符:类的属性字典中函数名为key,value为描述符类产生的对象)

    #非数据描述符
    class Lazyproperty:
        def __init__(self, func):
            self.func = func
    
        def __get__(self, instance, owner):
            if not instance:
                return self   #当Room调用area方法时,传入的instance为空,此时返回值为Lazyproperty的对象
            setattr(instance, self.func.__name__, self.func(instance))  # self.func.__name__可取得area的函数名,这一步可将计算得到的值添加到r1的属性字典中
            return self.func(instance)
    
    
    class Room:
        def __init__(self, name, width, length):
            self.name = name
            self.width = width
            self.length = length
    
        @Lazyproperty   # area = Lazyproperty(area) ,给类增加描述符, area被lazyproperty的对象代理
        def area(self):
            return self.width * self.length
    
    r1 = Room('主卧', 9, 8)
    print(r1.area)
    print(r1.__dict__)  # 第一次计算后,将'area':72直接放入r1的属性字典
    print(r1.area)  # r1再调用area方法时, 直接从自己的属性字典里面拿值,而不用再去触发装饰器Lazyproperty的get方法
    print(r1.area)
    print(Room.area)
    
    >>>
    72
    {'name': '主卧', 'width': 9, 'length': 8, 'area': 72}
    72
    72
    <__main__.Lazyproperty object at 0x0000017BC0DC69E8>

    不将描述符设置为数据描述符的原因

    class Lazyproperty:
        def __init__(self, func):
            self.func = func
    
        def __get__(self, instance, owner):
            print('每次计算都来找get方法')
            if not instance:
                return self   #当Room调用area方法时,传入的instance为空,此时返回值为Lazyproperty的对象
            setattr(instance, self.func.__name__, self.func(instance))  # self.func.__name__可取得area的函数名,这一步可将计算得到的值添加到r1的属性字典中
            return self.func(instance)
    
        def __set__(self, instance, value):
            pass
    
    class Room:
        def __init__(self, name, width, length):
            self.name = name
            self.width = width
            self.length = length
    
        @Lazyproperty   # area = Lazyproperty(area) ,给类增加描述符, area被lazyproperty的对象代理
        def area(self):
            return self.width * self.length
    
    r1 = Room('主卧', 9, 8)
    print(r1.area)
    print(r1.__dict__)  # 第一次计算后,将'area':72直接放入r1的属性字典
    print(r1.area)  # r1再调用area方法时, 由于数据描述符优先级比实例高,所以又要去触发描述符的get方法
    print(r1.area)
    print(Room.area)
    
    >>>
    每次计算都来找get方法
    72
    {'name': '主卧', 'width': 9, 'length': 8}
    每次计算都来找get方法
    72
    每次计算都来找get方法
    72
    每次计算都来找get方法
    <__main__.Lazyproperty object at 0x0000020F87AC6A90>

    8 利用描述符原理完成一个自定制@classmethod

    class ClassMethod:
        def __init__(self,func):
            self.func=func
    
        def __get__(self, instance, owner): #类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身,
            def feedback():
                print('在这里可以加功能啊...')
                return self.func(owner)
            return feedback
    
    class People:
        name='linhaifeng'
        @ClassMethod # say_hi=ClassMethod(say_hi)
        def say_hi(cls):
            print('你好啊,帅哥 %s' %cls.name)
    
    People.say_hi()
    
    p1=People()
    p1.say_hi()
    #疑问,类方法如果有参数呢,好说,好说
    
    class ClassMethod:
        def __init__(self,func):
            self.func=func
    
        def __get__(self, instance, owner): #类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身,
            def feedback(*args,**kwargs):
                print('在这里可以加功能啊...')
                return self.func(owner,*args,**kwargs)
            return feedback
    
    class People:
        name='linhaifeng'
        @ClassMethod # say_hi=ClassMethod(say_hi)
        def say_hi(cls,msg):
            print('你好啊,帅哥 %s %s' %(cls.name,msg))
    
    People.say_hi('你是那偷心的贼')
    
    p1=People()
    p1.say_hi('你是那偷心的贼')

    9 利用描述符原理完成一个自定制的@staticmethod

    class StaticMethod:
        def __init__(self,func):
            self.func=func
    
        def __get__(self, instance, owner): #类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身,
            def feedback(*args,**kwargs):
                print('在这里可以加功能啊...')
                return self.func(*args,**kwargs)
            return feedback
    
    class People:
        @StaticMethod# say_hi=StaticMethod(say_hi)
        def say_hi(x,y,z):
            print('------>',x,y,z)
    
    People.say_hi(1,2,3)
    
    p1=People()
    p1.say_hi(4,5,6)

    10. 再看property

    用法一

    class Foo:
        @property
        def AAA(self):
            print('get的时候运行我啊')
    
        @AAA.setter
        def AAA(self,value):
            print('set的时候运行我啊')
    
        @AAA.deleter
        def AAA(self):
            print('delete的时候运行我啊')
    
    #只有在属性AAA定义property后才能定义AAA.setter,AAA.deleter
    f1=Foo()
    f1.AAA
    f1.AAA='aaa'
    del f1.AAA
    
    >>>
    get的时候运行我啊
    set的时候运行我啊
    delete的时候运行我啊

    用法二

    class Foo:
        def get_AAA(self):
            print('get的时候运行我啊')
    
        def set_AAA(self, value):
            print('set的时候运行我啊')
    
        def delete_AAA(self):
            print('delete的时候运行我啊')
        AAA = property(get_AAA, set_AAA, delete_AAA)  # 内置property三个参数与get,set,delete一一对应
    
    f1=Foo()
    f1.AAA
    f1.AAA = 'aaa'
    del f1.AAA
    
    >>>
    get的时候运行我啊
    set的时候运行我啊
    delete的时候运行我啊

    案例一

    class Goods:
    
        def __init__(self):
            # 原价
            self.original_price = 100
            # 折扣
            self.discount = 0.8
    
        @property
        def price(self):
            # 实际价格 = 原价 * 折扣
            new_price = self.original_price * self.discount
            return new_price
    
        @price.setter
        def price(self, value):
            self.original_price = value
    
        @price.deleter
        def price(self):
            del self.original_price
    
    
    obj = Goods()
    obj.price         # 获取商品价格
    obj.price = 200   # 修改商品原价
    print(obj.price)
    del obj.price     # 删除商品原价
    
    >>>
    160.0
    set------>
    get------>

    案例二

    class People:
        def __init__(self, name):
            self.name = name
    
        @property
        def name(self):
            return self.name
    
    #p1 = People('alex') #property自动实现了set和get方法属于数据描述符,比实例属性优先级高,所以你这面写会触发property内置的set,抛出异常
    
    
    class People:
        def __init__(self, name):
            self.name = name  # 实例化就触发property
    
        @property
        def name(self):
            # return self.name #无限递归
            print('get------>')
            return self.DouNiWan
    
        @name.setter
        def name(self, value):
            print('set------>')
            self.DouNiWan = value
    
        @name.deleter
        def name(self):
            print('delete------>')
            del self.DouNiWan
    
    
    p1 = People('alex')  # self.name实际是存放到self.DouNiWan里
    print(p1.name)
    print(p1.name)
    print(p1.name)
    print(p1.__dict__)
    
    p1.name = 'egon'
    print(p1.__dict__)
    
    del p1.name
    print(p1.__dict__)
    
    >>>
    alex
    get------>
    alex
    get------>
    alex
    {'DouNiWan': 'alex'}
    set------>
    {'DouNiWan': 'egon'}
    delete------>
    {}

     

  • 相关阅读:
    initwithcoder和 initwithframe 区别?
    iOS图形处理和性能
    iOS图形处理和性能
    Objc的底层并发API
    Objc的底层并发API
    位运算
    位运算
    网页开发的6种在线调试环境
    网页开发的6种在线调试环境
    Python基本语法_函数属性 & 参数类型 & 偏函数的应用
  • 原文地址:https://www.cnblogs.com/cjsword/p/10664189.html
Copyright © 2020-2023  润新知