zoukankan      html  css  js  c++  java
  • 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和上面的含义相同

  • 相关阅读:
    POJ 2923 Relocation (状态压缩,01背包)
    HDU 2126 Buy the souvenirs (01背包,输出方案数)
    hdu 2639 Bone Collector II (01背包,求第k优解)
    UVA 562 Dividing coins (01背包)
    POJ 3437 Tree Grafting
    Light OJ 1095 Arrange the Numbers(容斥)
    BZOJ 1560 火星藏宝图(DP)
    POJ 3675 Telescope
    POJ 2986 A Triangle and a Circle
    BZOJ 1040 骑士
  • 原文地址:https://www.cnblogs.com/jessonluo/p/4758662.html
Copyright © 2011-2022 走看看