描述符
| 描述符 |
描述符也是面向進(jìn)階的一種,由于它的涉及比較廣,所以單獨(dú)講。
一、描述符
描述符本質(zhì)就是一個(gè)新式類,在這個(gè)新式類中,至少實(shí)現(xiàn)了__get__(),__set__(),__delete__()中的一個(gè),這也被稱為描述符協(xié)議。
描述符的作用是用來代理另外一個(gè)類的屬性,必須把描述符定義成一個(gè)類的類屬性,不能定義到構(gòu)造函數(shù)中。
描述符分為兩種,一種是數(shù)據(jù)描述符:至少實(shí)現(xiàn)了__get__()和__set__();另一種是非數(shù)據(jù)描述符:沒有實(shí)現(xiàn)__set__()。
1.描述符格式
class Foo: #在python3中任何類都是新式類,它只要實(shí)現(xiàn)了三種方法之一,那么這個(gè)類就被稱作一個(gè)描述符def __get__(self, instance, owner): #調(diào)用一個(gè)屬性時(shí)觸發(fā)passdef __set__(self, instance, value): #為一個(gè)屬性賦值時(shí)觸發(fā)passdef __delete__(self, instance): #采用del刪除屬性時(shí)觸發(fā)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() #把描述符代理一個(gè)類的屬性def __init__(self,n): self.x = nb1 = Bar(10) b1.x = 11111 b1.x del b1.x執(zhí)行代碼結(jié)果為:
我是set方法 我是set方法 我是get方法 我是delete方法可以看出在實(shí)例化時(shí)也會(huì)觸發(fā)__set__方法,因?yàn)樵陬怋ar的初始化函數(shù)中self.x是被描述符代理的屬性。那么對(duì)象b1中的屬性字典中到底存不存在x這個(gè)值呢?具體代碼如下所示:
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__)執(zhí)行結(jié)果為:
我是set方法 {} 我是set方法 {}這是什么情況?無論是實(shí)例化操作還是賦值操作,在對(duì)象b1的屬性字典中仍然為空。正是因?yàn)槊枋龇年P(guān)系,它相當(dāng)于把被描述符的類的調(diào)用屬性操作、賦值操作、刪除操作都賦予另一個(gè)類來實(shí)現(xiàn),跟本身類并沒有關(guān)系,當(dāng)然這關(guān)系到優(yōu)先級(jí)的問題。
3.描述符的優(yōu)先級(jí)
我們要嚴(yán)格遵守優(yōu)先級(jí):類屬性>數(shù)據(jù)描述符>實(shí)例屬性>非數(shù)據(jù)描述符>找不到的屬性觸發(fā)__getattr__()。
(1)我們先對(duì)類屬性>數(shù)據(jù)描述符進(jìn)行分析,具體代碼如下:
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) #結(jié)果為:111上述代碼打印結(jié)果仍為111,并沒有執(zhí)行數(shù)據(jù)描述符,是因?yàn)锽ar類調(diào)用了本來要被描述符代理的屬性x進(jìn)行了修改。所有類屬性的優(yōu)先級(jí)比數(shù)據(jù)描述符高。
(2)數(shù)據(jù)描述符>實(shí)例屬性的分析代碼如下:
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 #結(jié)果為:我是get方法 b1.x = 111 #結(jié)果為:我是set方法上述代碼Foo類被定義成Bar類的類屬性,即對(duì)Bar類進(jìn)行實(shí)例化,實(shí)例屬性只執(zhí)行數(shù)據(jù)描述符的方法。說明了數(shù)據(jù)描述符的優(yōu)先級(jí)高于實(shí)例屬性。
(3)實(shí)例屬性>非數(shù)據(jù)描述符的分析代碼如下:
class Foo():def __get__(self, instance, owner):print('我是get方法') class Bar:x = Foo()b1 = Bar() b1.x = 111 print(b1.__dict__) #結(jié)果為:{'x': 111}上述代碼可以看出實(shí)例給自己屬性進(jìn)行賦值操作,可以在實(shí)例的屬性字典中找到,這說明了實(shí)例屬性的優(yōu)先級(jí)高于非數(shù)據(jù)描述符。
(4)非數(shù)據(jù)描述符>找不到的屬性觸發(fā)__getattr__()的分析代碼如下:
class Foo():def __get__(self, instance, owner):print('我是get方法') class Bar:x = Foo()def __getattr__(self, item):print('我是getattr方法') b1 = Bar() b1.x #結(jié)果為:我是get方法 b1.name #結(jié)果為:我是getattr方法上述代碼可以看出找得到時(shí)觸發(fā)__get__方法,找不到就會(huì)觸發(fā)__getattr__方法。說明了非數(shù)據(jù)描述符的優(yōu)先級(jí)高于找不到的屬性觸發(fā)__getattr__()。
4.描述符的應(yīng)用
描述符可以應(yīng)用到哪些場(chǎng)合呢?我們就來舉個(gè)例子,通過描述符機(jī)制來實(shí)現(xiàn)參數(shù)的賦值類型限制。即:
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) #不符合賦值類型,拋出異常上述代碼對(duì)象p1是滿足參數(shù)的賦值類型,所以會(huì)觸發(fā)三次__set__方法。而對(duì)象p2不符合賦值類型,就會(huì)拋出異常。
5.propetry
一個(gè)靜態(tài)屬性property本質(zhì)就是實(shí)現(xiàn)了__get__,__set__,__delete__三種方法。
propetry有兩種用法,第一種即:
class Foo:@property #靜態(tài)屬性def AAA(self):print('get的時(shí)候運(yùn)行')@AAA.setterdef AAA(self,value):print('set的時(shí)候運(yùn)行',value)@AAA.deleterdef AAA(self):print('delete的時(shí)候運(yùn)行') f1 = Foo() f1.AAA f1.AAA='aaa' del f1.AAA執(zhí)行結(jié)果為:
get的時(shí)候運(yùn)行 set的時(shí)候運(yùn)行 aaa delete的時(shí)候運(yùn)行上述代碼中只有在屬性AAA定義property后才能定義AAA.setter,AAA.deleter。
第二種用法的代碼實(shí)現(xiàn)如下:
class Foo:def get_AAA(self):print('get的時(shí)候運(yùn)行')def set_AAA(self,value):print('set的時(shí)候運(yùn)行',value)def del_AAA(self):print('delete的時(shí)候運(yùn)行')AAA = property(get_AAA,set_AAA,del_AAA) #內(nèi)置property三個(gè)參數(shù)與get,set,delete一一對(duì)應(yīng) f1 = Foo() f1.AAA f1.AAA='aaa' del f1.AAA執(zhí)行結(jié)果為:
get的時(shí)候運(yùn)行 set的時(shí)候運(yùn)行 aaa delete的時(shí)候運(yùn)行上述兩種用法的結(jié)果都是一樣的。
我們也可以利用描述符自定制property,實(shí)現(xiàn)延時(shí)計(jì)算。我們先對(duì)一段代碼進(jìn)行分析,來看看我們利用描述符自定制property該如何實(shí)現(xiàn)相同的目的。即:
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) #結(jié)果為:240在上述代碼中@property相當(dāng)于實(shí)現(xiàn)了area=property(area),而property它是一個(gè)類,這是不是相當(dāng)于property類定義成Room類的類屬性,這不就是描述符的性質(zhì)嗎?它既然是描述符,那么它是數(shù)據(jù)描述符還是非數(shù)據(jù)描述符呢?從結(jié)果它打印實(shí)例屬性可以看出,它是非數(shù)據(jù)描述符,因?yàn)閷?shí)例屬性的優(yōu)先級(jí)>非數(shù)據(jù)描述符。假如我們自定制property,不采用內(nèi)置的靜態(tài)屬性property,該如何實(shí)現(xiàn)上述代碼,即:
class Lazyproperty:def __init__(self,func):self.func = funcdef __get__(self, instance, owner):return self.func(instance) #instance實(shí)例本身 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) #結(jié)果為:240上述代碼中的@Lazyproperty是我自定制的,當(dāng)然Lazyproperty類中我們只能定義成非數(shù)據(jù)描述符,否則不會(huì)執(zhí)行area方法。
我們繼續(xù)利用自定制property來實(shí)現(xiàn)延時(shí)計(jì)算功能:
class Lazyproperty:def __init__(self,func):self.func = funcdef __get__(self, instance, owner):print('get')if instance is None: #被類調(diào)用必須寫,否則報(bào)錯(cuò),因?yàn)轭悰]有實(shí)例return selfres = self.func(instance) #instance實(shí)例本身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) #從字典里先找,因?yàn)閷?shí)例屬性>非數(shù)據(jù)描述符,沒有再去類的中找,然后出發(fā)了area的__get__方法 print(r1.area) #先從自己的屬性字典找,找到了,是上次計(jì)算的結(jié)果,這樣就不用每執(zhí)行一次都去計(jì)算上述代碼實(shí)現(xiàn)了延時(shí)計(jì)算功能,這樣這不會(huì)每次都打印get的操作。描述符是可以實(shí)現(xiàn)大部分python類特性中的底層魔法,包括@classmethod,@staticmethd,@property甚至是__slots__屬性。
?6.類的裝飾器
類的裝飾器與函數(shù)的裝飾器性質(zhì)是一樣的,類的裝飾器分為無參裝飾器和有參裝飾器。
我們先來定義一個(gè)無參的裝飾器,即:
def deco(func):print('============')func.x = 1func.y = 2return func @deco #相當(dāng)于Foo = deco(Foo) class Foo:pass print(Foo.__dict__)執(zhí)行結(jié)果為:
============ {'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None, 'x': 1, 'y': 2}上述代碼當(dāng)程序遇到@deco就馬上執(zhí)行了Foo=deco(Foo),可以在結(jié)果可以看出Foo類中的屬性字典中有鍵值對(duì)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)執(zhí)行的結(jié)果為:
{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None, 'x': 1, 'y': 2, 'z': 3} alex上述代碼定義了有參的裝飾器,函數(shù)Typed是用來接收所有參數(shù)的。
下面我們利用裝飾器的應(yīng)用以及描述符來實(shí)現(xiàn)參數(shù)的賦值類型限制,即:
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)實(shí)例化時(shí)觸發(fā)了三次__set__方法。在描述符里也規(guī)定了參數(shù)的賦值類型。
轉(zhuǎn)載于:https://www.cnblogs.com/lzc69/p/11316469.html
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
- 上一篇: [欧拉函数] Bzoj P2186 沙拉
- 下一篇: 所谓经济现象