描述符
| 描述符 |
描述符也是面向進階的一種,由于它的涉及比較廣,所以單獨講。
一、描述符
描述符本質就是一個新式類,在這個新式類中,至少實現了__get__(),__set__(),__delete__()中的一個,這也被稱為描述符協議。
描述符的作用是用來代理另外一個類的屬性,必須把描述符定義成一個類的類屬性,不能定義到構造函數中。
描述符分為兩種,一種是數據描述符:至少實現了__get__()和__set__();另一種是非數據描述符:沒有實現__set__()。
1.描述符格式
class Foo: #在python3中任何類都是新式類,它只要實現了三種方法之一,那么這個類就被稱作一個描述符def __get__(self, instance, owner): #調用一個屬性時觸發passdef __set__(self, instance, value): #為一個屬性賦值時觸發passdef __delete__(self, instance): #采用del刪除屬性時觸發pass2.描述符的使用
描述符該如何使用呢?具體代碼如下:
class Foo: #描述符def __get__(self, instance, owner):print('我是get方法')def __set__(self, instance, value):print('我是set方法')def __delete__(self, instance):print('我是delete方法') class Bar:x = Foo() #把描述符代理一個類的屬性def __init__(self,n): self.x = nb1 = Bar(10) b1.x = 11111 b1.x del b1.x執行代碼結果為:
我是set方法 我是set方法 我是get方法 我是delete方法可以看出在實例化時也會觸發__set__方法,因為在類Bar的初始化函數中self.x是被描述符代理的屬性。那么對象b1中的屬性字典中到底存不存在x這個值呢?具體代碼如下所示:
class Foo(): #描述符def __get__(self, instance, owner):print('我是get方法')def __set__(self, instance, value):print('我是set方法')def __delete__(self, instance):print('我是delete方法') class Bar:x = Foo()def __init__(self,n): self.x = nb1 = Bar(10) print(b1.__dict__) b1.x = 11111 print(b1.__dict__)執行結果為:
我是set方法 {} 我是set方法 {}這是什么情況?無論是實例化操作還是賦值操作,在對象b1的屬性字典中仍然為空。正是因為描述符的關系,它相當于把被描述符的類的調用屬性操作、賦值操作、刪除操作都賦予另一個類來實現,跟本身類并沒有關系,當然這關系到優先級的問題。
3.描述符的優先級
我們要嚴格遵守優先級:類屬性>數據描述符>實例屬性>非數據描述符>找不到的屬性觸發__getattr__()。
(1)我們先對類屬性>數據描述符進行分析,具體代碼如下:
class Foo():def __get__(self, instance, owner):print('我是get方法')def __set__(self, instance, value):print('我是set方法')def __delete__(self, instance):print('我是delete方法') class Bar:x = Foo()Bar.x=111 print(Bar.x) #結果為:111上述代碼打印結果仍為111,并沒有執行數據描述符,是因為Bar類調用了本來要被描述符代理的屬性x進行了修改。所有類屬性的優先級比數據描述符高。
(2)數據描述符>實例屬性的分析代碼如下:
class Foo():def __get__(self, instance, owner):print('我是get方法')def __set__(self, instance, value):print('我是set方法')def __delete__(self, instance):print('我是delete方法') class Bar:x = Foo()b1 = Bar() b1.x #結果為:我是get方法 b1.x = 111 #結果為:我是set方法上述代碼Foo類被定義成Bar類的類屬性,即對Bar類進行實例化,實例屬性只執行數據描述符的方法。說明了數據描述符的優先級高于實例屬性。
(3)實例屬性>非數據描述符的分析代碼如下:
class Foo():def __get__(self, instance, owner):print('我是get方法') class Bar:x = Foo()b1 = Bar() b1.x = 111 print(b1.__dict__) #結果為:{'x': 111}上述代碼可以看出實例給自己屬性進行賦值操作,可以在實例的屬性字典中找到,這說明了實例屬性的優先級高于非數據描述符。
(4)非數據描述符>找不到的屬性觸發__getattr__()的分析代碼如下:
class Foo():def __get__(self, instance, owner):print('我是get方法') class Bar:x = Foo()def __getattr__(self, item):print('我是getattr方法') b1 = Bar() b1.x #結果為:我是get方法 b1.name #結果為:我是getattr方法上述代碼可以看出找得到時觸發__get__方法,找不到就會觸發__getattr__方法。說明了非數據描述符的優先級高于找不到的屬性觸發__getattr__()。
4.描述符的應用
描述符可以應用到哪些場合呢?我們就來舉個例子,通過描述符機制來實現參數的賦值類型限制。即:
class Typed:def __init__(self,key,expected_type):self.key = keyself.expected_type = expected_typedef __get__(self, instance, owner):print('get方法')return instance.__dict__[self.key]def __set__(self, instance, value):print('set方法')if not isinstance(value,self.expected_type):raise TypeError('你傳入的類型不是%s' %self.expected_type)instance.__dict__[self.key] = valuedef __delete__(self, instance):print('delete方法')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 = nameself.age = ageself.salary = salaryp1 = People('alex',18,6666.66) p2 = People(250,26,4568.55) #不符合賦值類型,拋出異常上述代碼對象p1是滿足參數的賦值類型,所以會觸發三次__set__方法。而對象p2不符合賦值類型,就會拋出異常。
5.propetry
一個靜態屬性property本質就是實現了__get__,__set__,__delete__三種方法。
propetry有兩種用法,第一種即:
class Foo:@property #靜態屬性def AAA(self):print('get的時候運行')@AAA.setterdef AAA(self,value):print('set的時候運行',value)@AAA.deleterdef AAA(self):print('delete的時候運行') f1 = Foo() f1.AAA f1.AAA='aaa' del f1.AAA執行結果為:
get的時候運行 set的時候運行 aaa delete的時候運行上述代碼中只有在屬性AAA定義property后才能定義AAA.setter,AAA.deleter。
第二種用法的代碼實現如下:
class Foo:def get_AAA(self):print('get的時候運行')def set_AAA(self,value):print('set的時候運行',value)def del_AAA(self):print('delete的時候運行')AAA = property(get_AAA,set_AAA,del_AAA) #內置property三個參數與get,set,delete一一對應 f1 = Foo() f1.AAA f1.AAA='aaa' del f1.AAA執行結果為:
get的時候運行 set的時候運行 aaa delete的時候運行上述兩種用法的結果都是一樣的。
我們也可以利用描述符自定制property,實現延時計算。我們先對一段代碼進行分析,來看看我們利用描述符自定制property該如何實現相同的目的。即:
class Room:def __init__(self,name,width,length):self.name = nameself.width = widthself.length = length@property #area=property(area)def area(self):return self.width*self.length r1 = Room('別墅',15,16) print(r1.area) #結果為:240在上述代碼中@property相當于實現了area=property(area),而property它是一個類,這是不是相當于property類定義成Room類的類屬性,這不就是描述符的性質嗎?它既然是描述符,那么它是數據描述符還是非數據描述符呢?從結果它打印實例屬性可以看出,它是非數據描述符,因為實例屬性的優先級>非數據描述符。假如我們自定制property,不采用內置的靜態屬性property,該如何實現上述代碼,即:
class Lazyproperty:def __init__(self,func):self.func = funcdef __get__(self, instance, owner):return self.func(instance) #instance實例本身 class Room:def __init__(self,name,width,length):self.name = nameself.width = widthself.length = length@Lazyproperty #area=Lazyproperty(area)def area(self):return self.width*self.length r1 = Room('別墅',15,16) print(r1.area) #結果為:240上述代碼中的@Lazyproperty是我自定制的,當然Lazyproperty類中我們只能定義成非數據描述符,否則不會執行area方法。
我們繼續利用自定制property來實現延時計算功能:
class Lazyproperty:def __init__(self,func):self.func = funcdef __get__(self, instance, owner):print('get')if instance is None: #被類調用必須寫,否則報錯,因為類沒有實例return selfres = self.func(instance) #instance實例本身setattr(instance,self.func.__name__,res) #緩存return res class Room:def __init__(self,name,width,length):self.name = nameself.width = widthself.length = length@Lazyproperty #area=Lazyproperty(area)def area(self):return self.width*self.length r1 = Room('別墅',15,16) print(r1.area) #從字典里先找,因為實例屬性>非數據描述符,沒有再去類的中找,然后出發了area的__get__方法 print(r1.area) #先從自己的屬性字典找,找到了,是上次計算的結果,這樣就不用每執行一次都去計算上述代碼實現了延時計算功能,這樣這不會每次都打印get的操作。描述符是可以實現大部分python類特性中的底層魔法,包括@classmethod,@staticmethd,@property甚至是__slots__屬性。
?6.類的裝飾器
類的裝飾器與函數的裝飾器性質是一樣的,類的裝飾器分為無參裝飾器和有參裝飾器。
我們先來定義一個無參的裝飾器,即:
def deco(func):print('============')func.x = 1func.y = 2return func @deco #相當于Foo = deco(Foo) class Foo:pass print(Foo.__dict__)執行結果為:
============ {'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None, 'x': 1, 'y': 2}上述代碼當程序遇到@deco就馬上執行了Foo=deco(Foo),可以在結果可以看出Foo類中的屬性字典中有鍵值對x與y。
下面我們來介紹有參的裝飾器該如何定義:
def Typed(**kwargs):def deco(obj):for key,val in kwargs.items():setattr(obj,key,val)return objreturn deco@Typed(x=1,y=2,z=3) #1.Typed(x=1,y=2,z=3)--->deco 2.@deco----->Foo=deco(Foo) class Foo:pass print(Foo.__dict__)@Typed(name='alex') #@deco---->Bar=deco(Bar) class Bar:pass print(Bar.name)執行的結果為:
{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None, 'x': 1, 'y': 2, 'z': 3} alex上述代碼定義了有參的裝飾器,函數Typed是用來接收所有參數的。
下面我們利用裝飾器的應用以及描述符來實現參數的賦值類型限制,即:
class Typed:def __init__(self,key,expected_type):self.key = keyself.expected_type = expected_typedef __get__(self, instance, owner):print('get方法')return instance.__dict__[self.key]def __set__(self, instance, value):print('set方法')if not isinstance(value,self.expected_type):raise TypeError('你傳入的類型不是%s' %self.expected_type)instance.__dict__[self.key] = valuedef __delete__(self, instance):print('delete方法')instance.__dict__.pop(self.key)def deco(**kwargs):def wrapper(obj):for key,val in kwargs.items():setattr(obj,key,Typed(key,val)) #Typed(key,val)描述符return objreturn wrapper@deco(name = str,age = int,salary = float) class People:def __init__(self,name,age,salary):self.name = nameself.age = ageself.salary = salaryp1 = People('alex',20,6666.66)實例化時觸發了三次__set__方法。在描述符里也規定了參數的賦值類型。
轉載于:https://www.cnblogs.com/lzc69/p/11316469.html
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
- 上一篇: [欧拉函数] Bzoj P2186 沙拉
- 下一篇: 所谓经济现象