• Python属性、方法和类管理系列之----描述符类


    什么是描述符类?

    根据鸭子模型理论,只要具有__get__方法的类就是描述符类。
    如果一个类中具有__get____set__两个方法,那么就是数据描述符,。
    如果一个类中只有__get__方法,那么是非数据描述符。

    __get__:当我们用类或者实例来调用该属性时,Python会返回__get__函数的结果。
    __set__:当我们用实例来设置属性值时,Python会调用该函数。对类没有限制作用。
    __delete__:当我们用实例试图删除该属性时,Python会调用该函数。对类没有限制作用。

    非数据描述类

    class Desc:
        def __init__(self, value=22):
            self.value= value
        def __get__(self, ins, cls):
            return self.value
    
    class A:
        v=Desc()
    a=A()
    

    上面的描述符类只有一个__get__属性,所以是非数据描述符。

    >>> a.v             #由于实例中没有v属性,所以找到了类的属性,而类的属性是一个描述符类实例,所以调用其__get__方法的结果。
    22
    >>> a.__dict__      #实例的__dict__空空如也。
    {}
    >>> A.__dict__      #类的__dict__中确实存在v属性,且是一个Desc object对象。
    mappingproxy({'__module__': 'b', '__weakref__': <attribute '__weakref__' of 'A' objects>, '__dict__': <attribute '__dict__' of 'A' objects>, '__doc__': None, 'v': <b.Desc object at 0x7ff010f5f550>})
    >>> a.v=30          #我们通过实例设置v属性,发现成功了。
    >>> a.__dict__      #我们发现实例的__dict__中存入了我们刚才设置的属性
    {'v': 30}
    >>> A.__dict__      #类的__dict__没有发生任何变化
    mappingproxy({'__module__': 'b', '__weakref__': <attribute '__weakref__' of 'A' objects>, '__dict__': <attribute '__dict__' of 'A' objects>, '__doc__': None, 'v': <b.Desc object at 0x7ff010f5f550>})
    >>> a.v             #如我们所料,访问到了a.__dict__中的内容。             
    30
    >>> del a.v         #我们删除实例的属性v后发现居然还是可以调用a.v,返回的是我们设置之前的值。
    >>> a.v
    22
    >>> A.__dict__      #和前面一样,没有发生变化。
    mappingproxy({'__module__': 'b', '__weakref__': <attribute '__weakref__' of 'A' objects>, '__dict__': <attribute '__dict__' of 'A' objects>, '__doc__': None, 'v': <b.Desc object at 0x7ff010f5f550>})
    

    通过上面的测试,我们发现非数据描述类有如下特点:

    • 如果实例__dict__没有设置同名属性,那么返回描述类的__get__方法的结果。
    • 如果实例__dict__中存在同名属性,那么返回实例__dict__中的内容。
    • 对我们设置实例的__dict__中的行为并不做阻止。所以我说这是查看级别的描述类。

    数据描述类

    class Desc:
        def __init__(self, value=22):
            self.value= value
        def __get__(self, ins, cls):
            return self.value
        def __set__(self, ins, value):
            self.value=value
            #raise AttributeError
    
    class A:
        v=Desc()
    a=A()
    
    

    运行结果如下:

    >>> a.v
    22
    >>> a.v=10
    >>> a.__dict__      #我们设置a.v后,发现实例的__dict__中仍然空空如也。因为此时调用的是__set__方法,值10存入到了Desc实例的value属性上了。
    {}
    >>> A.__dict__
    mappingproxy({'__module__': 'b', '__doc__': None, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, 'v': <b.Desc object at 0x7f0d2a4de5c0>})
    >>> a.v             #此时得到的还是Desc的__get__方法返回的结果。
    10
    >>> del a.v         #不允许我们删除
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: __delete__
    >>> A.v=30
    >>> A.__dict__
    mappingproxy({'__module__': 'b', '__doc__': None, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, 'v': 30})
    #我们把__set__方法的原来语句注销,添加raise AttribeError语句,再次运行
    >>> a.v=30          #我们在__set__中手动添加了AttributeError异常,所以我们再也不能设置a.v的值了,因此该属性鞭策了只读属性。
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "/home/aaa/proj/b.py", line 8, in __set__
        raise AttributeError
    AttributeError
    >>> A.v=20          #通过类,仍然可以改变属性
    >>> A.__dict__      #改变后,变成了普通属性20了,这时甚至都已经不再是描述符类了。
    mappingproxy({'__doc__': None, '__dict__': <attribute '__dict__' of 'A' objects>, '__module__': 'b', '__weakref__': <attribute '__weakref__' of 'A' objects>, 'v': 20})
    >>> del A.v
    

    说明如下:

    • __set__方法存在后,实例设置同名属性时,完全需要看__set__的脸色。
    • 如果描述类中__set__方法存在但是__delete__方法不存在,那么不能删除客户类中的属性。
    • 即使在__set__方法中做了限制,这个限制只是对实例而言的,对类没有起到作用。

    把属性存在描述符类中

    class Desc:
        def __init__(self, value):
            self.value = value
        def __get__(self, ins, cls):
            return self.value
        def __set__(self, ins, value):
            self.value = value
        def __delete__(self, ins):
            raise AttributeError('not allowed to delete attribute name ' )
    
    class A:
        name=Desc('JS')
    a=A()
    

    执行结果如下:

    >>> del a.name
    >>> a=A()
    >>> b=A()
    >>> a.name
    'JS'
    >>> b.name
    'JS'
    >>> a.name='CC'
    >>> b.name
    'CC'
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "/home/aaa/proj/b.py", line 10, in __delete__
        raise AttributeError('not allowed to delete attribute name ' )
    AttributeError: not allowed to delete attribute name
    

    缺点显而易见,如果有多个实例,那么他们共享一个描述符,所以当一个实例的该属性发生改变后,其他实例的该属性也会发生变化。

    改善方法:
    存入一个字典,把实例的hash作为健存入,这样可以解决问题。

    class Desc:
        def __init__(self, value):
            self.values={}
        def __get__(self, ins, cls):
            return self.values[hash(ins)] 
        def __set__(self, ins, value):
            self.values[hash(ins)]=value
        def __delete__(self, ins):
            raise AttributeError('not allowed to delete attribute name ' )
    

    把数据存入实例中

    class Desc:
        def __get__(self, ins, cls):
            return ins._name
        def __set__(self, ins, value):
            ins._name=value 
        def __delete__(self, ins):
            raise AttributeError('not allowed to delete attribute name ' )
    
    class A:
        name=Desc()
    a=A()
    

    执行结果如下:

    >>> a=A()
    >>> a.name='JS'
    >>> a.name
    'JS'
    >>> a._name='CC'
    >>> a.name
    'CC'
    

    缺点:我们设置在实例中的变量私密性不太好,可以很容易被改变。
    当然,可以做一个私有性的装饰器,或者利用属性扩张来解决,这是我在后面会介绍的内容。

    补充解释

    __get__(self, ins, cls):其中ins为实例对象,在我们上面的例子中是a或者b,cls为a或者b的类,为A
    __set____delete__:ins和上面的含义相同

  • 相关阅读:
    PAT甲级1018Public Bike Management
    PAT甲级1003Emergency
    android的AIDL
    View的滑动冲突和解决方案
    弹性滑动原理
    View的滑动原理和多种滑动方法
    Android的Activity的生命周期
    Android获取App版本号和版本名
    转:Android检查设备是否联网
    androidstudio实现增量更新步骤
  • 原文地址:https://www.cnblogs.com/jessonluo/p/4758662.html
Copyright © 2020-2023  润新知