根據鴨子模型理論,只要具有__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.valueclass 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.v22>>> 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 AttributeErrorclass A: v=Desc()a=A()運行結果如下:
>>> a.v22>>> 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 AttributeErrorAttributeError>>> 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和上面的含義相同
新聞熱點
疑難解答