亚洲精品久久久中文字幕-亚洲精品久久片久久-亚洲精品久久青草-亚洲精品久久婷婷爱久久婷婷-亚洲精品久久午夜香蕉

您的位置:首頁技術文章
文章詳情頁

Python descriptor(描述符)的實現

瀏覽:4日期:2022-07-05 14:15:55

問題

問題1

Python是一種動態語言,不支持類型檢查。當需要對一個對象執行類型檢查時,可能會采用下面的方式:

class Foo(object): def __init__(self,a): if isinstance(a,int): self.__a = a else: raise TypeError('Must be an int') def set_a(self,val): if isinstance(val,int): self.__a = val else: raise TypeError('Must be an int') def get_a(self): return self.__a

上述是一種類型檢查的方法,但是如果需要類型檢查的參數非常多的話就會變得非常繁瑣,重復代碼太多,Python這么簡潔,優雅,肯定有更好的解決方法。另外上述方法也有缺陷。

f = Foo(1)print f.get_a() #1print f._Foo__a #1,還是能訪問到= =f._Foo__a = ’test’print f.get_a() #test,還是改變了__a的值,而且不是int型print f._Foo__a #test

問題2

在一個對象中,創建一個只讀屬性。

問題3

class Foo(object): a = 1f = Foo()print f.a #1,實例屬性中沒有a屬性,所以到Foo.__dict__中查找print Foo.a #1print f.__dict__ #{}f.a = 2 #增加一個名為a的實例屬性print f.a #2,搜索屬性時先在實例字典中查找,然后再去類的字典中查找,在實例的__dict__中找到了..print Foo.a #1print f.__dict__ #{’a’: 2}

如果不想給實例f增加實例屬性,而是想對類屬性操作怎么辦呢。解決方案:

1) 使用class.attr改變值;

​ Foo.a = 2就不會給實例增加實例屬性了。

2) 自定義屬性訪問,描述符;

class descriptor(object): def __init__(self,val): self.val = val def __get__(self,obj,type = None): print ’get’, return self.val def __set__(self,obj,val): print ’set’,val self.val = val def __delete__(self,obj): raise AttributeError('Can’t delete attribute')class Foo(object): a = descriptor(0)f = Foo()print f.a #get 0print Foo.a #get 0print f.__dict__ #{}f.a = 2 #set 2,并沒有增加實例屬性print f.a #get 2print Foo.a #get 2print f.__dict__ #{}

問題總結

上述三個問題均與屬性訪問有關,如果能夠自定義屬性訪問,上述問題就能解決啦= =。其實問題3已經給出了解決方法,就是描述符…

描述符的定義和介紹

​ 描述符(Descriptor)是Python中非常重要的一部分,它廣泛應用于Python的內核。

​ 一般來說,描述符就是一個帶有綁定方法的對象,只不過照比其他對象多了幾個特殊的描述符方法,即 __get__(),__set__(),__delete__() 。對描述符對象的屬性訪問主要通過描述符方法。

定義:一個對象如果定義了__get__(),__set__(),__delete__()方法中的任何一個,它就可以被稱為描述符。

​ 對屬性的訪問默認是從對象的字典中獲取(get),設置(set)和刪除(delete)屬性。

假設有實例a,a有屬性x,獲取a.x的值。

一般來說,a.x會按以下順序查找屬性,查找鏈: a.__dict__[’x’] → type(a).__dict__[’x’] → 根據mro順序在type(a)的父類中查找(不包括元類) 。

​ 但是,如果被查找的屬性是一個描述符,并且為類屬性,那么就會覆蓋其默認行為,轉而去調用描述符方法。注意,描述符僅適用于新式類和新式對象。

class descriptor(object): def __init__(self,val): self.val = val def __get__(self, obj, objtype): print ’get’, return self.val def __set__(self, obj, val): print ’set’ self.val = valclass Foo(object): x = descriptor(0) y = 0f = Foo()’’’描述符覆蓋默認的訪問行為’’’print f.x #get 0,調用的是描述符的__get__函數print f.__dict__ #{}f.x = 1 #set,調用的是描述符的__set__函數print f.x #get 1print f.__dict__ #{},即使賦值也沒有增加實例屬性...,是不是覆蓋了默認行為- -f.y = 2 print f.__dict__ #{’y’: 2},因為沒有與y同名的描述符對象...

描述符十分強大。properties,methods, static methods,class methods, and super()都是基于描述符實現的。從Python2.2開始,描述符就在新式類中出現了。描述符是一個靈活的工具,使程序開發更加便利。感覺描述符很吊啊…

Descriptor Protocol(描述符協議)

descriptor.__get__(self, obj, type=None) --> value

descriptor.__set__(self, obj, value) --> None

descriptor.__delete__(self, obj) --> None

上述三個方法就是描述符方法,如果一個對象定義了描述符方法中的任何一個,那么這個對象就會成為描述符。

假設有一個對象t,t.a是一個定義了三個描述符方法的描述符,并且a是類屬性,調用情況如下:

print t.a → a.__get__(t, type(t))

t.a = v → a.__set__(t, v)

del t.a → a.__delete__(t)

注意:當描述符作為類屬性時,才會自動調用描述符方法,描述符作為實例屬性時,不會自動調用描述符方法。

數據和非數據描述符

data descriptor(數據描述符):定義了__get__()和__set__()方法的對象;

non-data descriptor(非數據描述符):僅定義了__get__()的對象;非數據描述符典型應用是class methods。

數據和非數據描述符的區別

如果一個實例的字典(__dict__)中有一個和數據描述符對象相同名字的實例屬性,則優先訪問數據描述符;

class descriptor(object): def __init__(self,val = 0): self.val = val def __get__(self, obj, objtype): print ’__get__’, return self.val def __set__(self, obj, val): self.val = valclass Foo(object): a = descriptor()#數據描述符f = Foo()f.a = 1 #不會增加實例屬性,會調用descriptor.__set__方法,數據描述符的優先級比實例屬性高print f.a #__get__ 1,調用的是descriptor.__get__方法print f.__dict__ #{}

如果一個實例中存在和數據描述符名字相同的實例屬性,利用下述方法就可以訪問實例屬性…

f.__dict__[’a’] = 2 #增加一個與數據描述符同名的實例屬性print f.__dict__ #{’a’: 2}print f.a #__get__ 1,正常使用,調用的仍是描述符的__get__方法print f.__dict__[’a’] #2,這個時候就是實例屬性了...

如果一個實例的字典中有一個和非數據描述符對象相同名字的實例屬性,則優先訪問實例屬性;

class descriptor(object): def __init__(self,val = 0): self.val = val def __get__(self, obj, objtype): print ’__get__’, return self.valclass Foo(object): a = descriptor()#非數據描述符,沒有__set__f = Foo()print f.a #__get__ 0,調用descriptor.__get__方法f.a = 1 #增加實例屬性,因為實例屬性的優先級比非數據描述符高print f.a #1,不會調用descriptor.__get__方法,實例屬性的優先級比非數據描述符高print f.__dict___ #{’a’: 1},增加了實例屬性a

數據和非數據描述符測試code

class data_descriptor(object):#數據描述符 def __init__(self,val): self.val = val def __get__(self, obj,type = None): print ’data get’ return self.val def __set__(self, obj, value): if not isinstance(value,int):#賦值時可以類型檢查啊 raise ValueError(’value must be int’) self.val = valueclass non_data_descriptor(object):#非數據描述符 def __init__(self,val): self.val = val def __get__(self,obj,type = None): print ’non data get’ return self.valclass Foo(object): data = data_descriptor(0) non_data = non_data_descriptor(1)f = Foo()print f.__dict__ #{}print f.data #data get,0print f.non_data #non data get,1f.data = 2 #數據描述符優先級較高,不會創建實例屬性,而是調用描述符的__set__方法f.non_data = 3 #增加了與非數據描述符同名的實例屬性print f.__dict__ #{’non_data’: 3}print f.data #data get,2 調用數據描述符的__get__()方法print f.non_data #3,非數據描述符優先級比實例屬性低print Foo.non_data #non data get 1,利用類屬性查找還是可以訪問非數據描述符的,非數據描述符值未改變

屬性訪問,__getattribute__()

​ 在Python中,__getattribute__()就是屬性解析機制,當調用一個屬性時,不管是成員還是方法,都會觸發 __getattribute__()來調用屬性。

屬性解析機制按照優先級鏈搜索屬性。在優先級鏈中,類字典中的數據描述符的優先級高于實例變量,實例屬性的優先級高于非數據描述符,如果定義了__getattr()__,優先級鏈會為__getattr()__分配最低優先級。可以通過自定義__getattribute__方法來重寫優先級鏈。

優先級鏈 : 數據描述符 > 實例屬性 > (非數據描述符,非描述符的類屬性) > __getattr()__

上述所說的描述符均要為類屬性,當描述符作為實例屬性出現時,不會自動調用描述符方法。

class Foo(object): def __init__(self): self.x = 1 self.y = 2 def __getattribute__(self,keys = None):#這樣優先級鏈,描述符什么的就都沒用了。。。。 return ’test’f = Foo()print f.x,f.y,f.z #test,test,test,優先級鏈什么的都沒用啦= =

__getattribute__()的Python實現大致如下,

def __getattribute__(self, key): 'Emulate type_getattro() in Objects/typeobject.c' v = object.__getattribute__(self, key) if hasattr(v, ’__get__’):#如果v定義了__get__方法的話,優先調用v.__get__ return v.__get__(None, self) return v

調用描述符

​ 描述符調用的細節取決于obj是一個對象還是一個類。不管是哪種,描述符只對新式對象和新式類起作用。繼承了 object 的類是新式類。

​ 描述符也是一個對象,可以通過方法名直接調用描述符方法,如描述符d,d.__get__(object)。另外,在訪問描述符時會自動調用相應的描述符方法(只有為類屬性時才會自動調用)。描述符的自動調用機制基于__getattribute__() , __getattribute__()確保了descriptor的機制,所以,如果重寫了__getattribute__, 就可以消除descriptor機制。

​ 對于對象來說,obj.__getattribute__()會將b.x轉化為 type(b).__dict__[’x’].__get__(b,type(b)) ,按照下述順序搜索屬性:

類屬性中的數據描述符 > 實例變量 > 類屬性的非數據描述符 > __getattr__()。

​ 對于類來說,class.__getattribute__()會將B.x轉化為B.__dict__[’x’].__get__(None,B) 。

記住以下幾點:

1.描述符的調用基于__getattribute__();2.重寫__getattribute__()會阻止描述符的正常調用;3.object.__getattribute__()和type.__getattribute__()會調用不同的__get__();4.數據描述符一直覆蓋實例屬性,即同時存在同名的數據描述符和實例屬性,優先調用數據描述符5.非數據描述符一直被實例屬性覆蓋,即同時存在同名的非數據描述符和實例屬性,優先調用實例屬性;

​ 描述符的機制在 object, type, 和 super 的 __getattribute__()方法中實現。由 object 派生出的類自動繼承這個機制,或者它們有個有類似機制的元類。如果想讓描述符失效的話,可以重寫 __getattribute__() 。

class descriptor(object): def __init__(self, initval=None, name=’var’): self.val = initval self.name = name def __get__(self, obj, objtype): print ’get’, self.name, return self.val def __set__(self, obj, val): print ’set’, self.name,val self.val = valclass Foo(object): x = descriptor(10, ’var 'x'’) y = 5m = Foo()’’’訪問m.x的三種方法’’’print m.x #get var 'x',10print type(m).__dict__[’x’].__get__(m,type(m)) #m.x會轉化為這種形式,等價于m.xprint m.__getattribute__(’x’) #等價于m.x,因為x定義了__get__方法,調用x的__get__方法,上面已經給出了__getattribute__的實現原理’’’設置m.x的值’’’m.x = 20#set var 'x' 20type(m).__dict__[’x’].__set__(m,20) #m.x = 20會轉化為此種形式,等價于m.x = 20print m.x #get var 'x',20print m.y #5#print type(m).__dict__[’y’].__get__(m,type(m)) #error,AttributeError: ’int’ object has no attribute ’__get__’print m.__getattribute__(’y’) #5,等價于m.y

描述符的陷阱

描述符應放在類層次上,即作為類屬性(class level)

說了N多次了,要謹記…

class Foo(object): y = descriptor(0) def __init__(self): self.x = descriptor(1) #實例屬性的描述符是不會自動調用對應的描述符方法的...b = Foo()print 'X is %s, Y is %s' % (b.x, b.y)#X is <__main__.descriptor object at 0x10432c250>, Y is 0print 'Y is %s'%b.y.__get__(b) #需要自己調用__get__方法,解釋器不會自己調用的

從上述代碼可知,

訪問類層次上的描述符會自動調用相應的描述符方法; 訪問實例層次上的描述符只會返回描述符對象自身,并不會調用相應的描述符方法;

(實例屬性的描述符不會自動調用描述符方法,這么做肯定是有原因的吧…有知道的大神求指導…)

**描述符是所有實例共享的,讓不同實例保存的值互不影響

class descriptor(object): def __init__(self, default): self.value = default def __get__(self, instance, owner): return self.value def __set__(self, instance, value): self.value = valueclass Foo(object): bar = descriptor(5)

bar是類屬性,所有Foo的實例共享bar。

f = Foo()g = Foo()print 'f.bar is %s g.bar is %s' % (f.bar, g.bar) #f.bar is 5 g.bar is 5f.bar = 10 #調用__set__函數print 'f.bar is %s g.bar is %s' % (f.bar, g.bar) #f.bar is 10 g.bar is 10

當實例修改了描述符以后,會影響到其他實例,有沒有一種方法可以讓實例之間互不影響呢?

數據字典法

​ 在descriptor中使用數據字典,由__get__和__set__的第一個參數來確定是哪個實例,使用實例作為字典的key,為每一個實例單獨保存一份數據,修改代碼如下…

from weakref import WeakKeyDictionaryclass descriptor(object): def __init__(self, default): self.default = default self.data = WeakKeyDictionary() def __get__(self, instance, owner):# instance = x,owner = type(x) # we get here when someone calls x.d, and d is a descriptor instance return self.data.get(instance, self.default) def __set__(self, instance, value): # we get here when someone calls x.d = val, and d is a descriptor instance self.data[instance] = valueclass Foo(object): bar = descriptor(5)f = Foo()g = Foo()print 'f.bar is %s g.bar is %s' % (f.bar, g.bar) #f.bar is 5 g.bar is 5print 'Setting f.bar to 10'f.bar = 10print 'f.bar is %sng.bar is %s' % (f.bar, g.bar) ##f.bar is 10 g.bar is 5

上述方法雖然可行,但是存在缺陷。

descriptor使用了一個字典來保存不同實例的數據。一般來說是不會出現問題,但是如果實例為不可哈希對象,如list,上述方法就會出現問題,因為不可哈希對象不能作為鍵值。

標簽法

說白了就是給實例增加一個與描述符同名的實例屬性,利用該實例屬性來保存該實例描述符的值,描述符相當于一個中間操作,描述符的__get__()返回實例屬性,__set__也是對實例屬性操作。

此方法的實現原理: 數據描述符的訪問優先級比實例屬性高…

還是見圖和代碼吧,代碼最直觀…

Python descriptor(描述符)的實現

class descriptor(object): def __init__(self, label):#label為給實例增加的實例屬性名 self.label = label def __get__(self, instance, owner): #dict.get(k[,d]) -> D[k] if k in D, else d. d defaults to None. return instance.__dict__.get(self.label) #獲取與描述符同名的實例屬性的值 def __set__(self, instance, value): #注意這里,要這么寫,不能寫instance.x = val這種形式,這樣會形成自身的循環調用 instance.__dict__[self.label] = value #修改與描述符同名的實例屬性的值 class Foo(list): x = descriptor(’x’) #注意這個初始化值為要給實例增加的實例屬性名,要和描述符對象同名。 y = descriptor(’y’)f1 = Foo()f2 = Foo()print f1.__dict__ #{}print f1.x,f2.x,f1.y,f2.y#None None None None,此時尚未增加實例屬性,需要調用__set__方法建立一個與描述符同名的實例屬性#print Foo.__dict__f1.x = 1f1.y = 2f2.x = 3f2.y = 4 print f1.__dict__ #{’y’: 2, ’x’: 1} #增加了的實例屬性print f1.x,f1.y,f2.x,f2.y #1 2 3 4

因為只有調用了__set__函數才會建立一個與描述符同名的實例屬性,所以可以在__init__()函數中對描述符賦值。

class Foo(list): x = descriptor(’x’) y = descriptor(’y’) def __init__(self): self.x = 1 #調用的是描述符的__set__方法,與描述符同名的實例屬性增加完畢.... self.y = 2f = Foo()print f.x,f.y

注意事項:

給描述符添加標簽時,初始化值要和描述符的變量名相同,比如name = descriptor(‘name’),因為這個初始化值是給實例增加的實例屬性名,必須要和描述符對象同名。

下面為錯誤示范,初始化值和描述符不同名的情況。

class descriptor(object): def __init__(self, label): self.label = label def __get__(self, instance, owner): return instance.__dict__.get(self.label) def __set__(self, instance, value): if not isinstance(value,int): raise ValueError(’must be int’) instance.__dict__[self.label] = value class Foo(object): x = descriptor(’y’) #應該改為Descriptor(’x’),與實例同名 def __init__(self,val = 0): self.x = valf = Foo()print f.x #0f.y = ’a’ #繞過了描述符的__set__方法...未進行類型檢查,此時x為非法值啊,是不是很坑...print f.x #a

潛在坑…正常使用時也會帶來坑。

class Foo(object): x = descriptor(’x’) def __init__(self,val = 0): self.x = valf = Foo()f.x = ’a’#ValueError: must be int

好像沒毛病啊…接著往下看。

f.__dict__[’x’] = ’a’print f.x #a,還是繞過了__set__方法,未進行類型檢查,還是非法值啊...哈哈- -

小結

​ 查了很多資料,標簽法用的較多,但是標簽法也有一定的缺陷,目前沒有找到更好的方法解決上述問題,如有更好的方法,求指導,謝謝…

描述符的應用…

​ 描述符也是類,可以為其增加方法。比如增加回調方法,描述符是一個用來回調的很好的手段。比如想要一個類的某個狀態發生變化就立刻通知我們,可以自定義回調函數用來響應類中的狀態變化。如以下代碼,

from weakref import WeakKeyDictionaryclass CallbackProperty(object): def __init__(self, default=None): self.data = WeakKeyDictionary() self.default = default self.callbacks = WeakKeyDictionary() def __get__(self, instance, owner): if instance is None: return selfreturn self.data.get(instance, self.default) def __set__(self, instance, value):#每次改變值的時候都會調用low_balance_warning函數 for callback in self.callbacks.get(instance, []): # alert callback function of new value callback(value) self.data[instance] = value def add_callback(self, instance, callback): '''Add a new function to call everytime the descriptor within instance updates''' if instance not in self.callbacks: self.callbacks[instance] = [] #實例->[方法,] self.callbacks[instance].append(callback)class BankAccount(object): balance = CallbackProperty(0)def low_balance_warning(value): if value < 100: print 'You are now poor' else: print ’You are now rich!!!’def check(value): print ’You have %s money, Good Luck!!!’%valueba = BankAccount()BankAccount.balance.add_callback(ba, low_balance_warning)BankAccount.balance.add_callback(ba, check)ba.balance = 5000 # You are now rich!!! You have 5000 money, Good Luck!!!print 'Balance is %s' % ba.balance # Balance is 5000ba.balance = 99 # You are now poor You have 99 money, Good Luck!!!print 'Balance is %s' % ba.balance # Balance is 99

有木有感覺很厲害…__set__()方法感覺就像一個監督人員,監視屬性的一舉一動。

​ 描述符還有其他用處,如格式檢查,類型檢查,設置只讀變量等。設置一個只讀變量的話,只要不讓變量再賦值就好了,即調用__set__()函數時觸發異常即可,這也是問題2的答案。

class descriptor(object): def __init__(self,val): self.val = val def __get__(self, obj,type = None): return self.val def __set__(self, obj, value): raise Exception(’read only’)class Foo(object): d = descriptor(1)d = Foo()print d.d #1d.d = 2 #觸發異常,read only

參考網址

1.https://docs.python.org/2/howto/descriptor.html#definition-and-introduction2.https://hg.python.org/cpython/file/2.7/Objects/object.c3.http://svn.python.org/view/python/trunk/Objects/classobject.c?view=markup4.http://www.geekfan.net/7862/5.http://pyzh.readthedocs.io/en/latest/Descriptor-HOW-TO-Guide.html6.//www.jb51.net/article/62987.htm7.//www.jb51.net/article/97741.htm8.http://www.tuicool.com/articles/yYJbqun

到此這篇關于Python descriptor(描述符)的實現的文章就介紹到這了,更多相關Python descriptor內容請搜索好吧啦網以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持好吧啦網!

標簽: Python 編程
相關文章:
主站蜘蛛池模板: 日韩欧美一二三 | 国产福利不卡视频在免费播放 | 无圣光私拍一区二区三区 | 9191久久久久视频 | 免费成人黄色片 | 国内不卡1区2区 | 51精品视频在线观看视频 | 亚洲欧美一区二区三区在线观看 | 亚1洲二区三区四区免费 | 亚洲欧美另类色图 | 一区二区三区视频观看 | 国产精品偷伦视频免费手机播放 | 6699久久久久久久77777'7 66av99精品福利视频在线 | 黄色综合网 | 青草香蕉精品视频在线观看 | 猫咪视频成人永久免费观看 | 一级生性活免费视频 | 亚洲一区二区成人 | 在线视频 自拍 | 日韩一区二区免费视频 | 特级黄色片视频 | 国产综合色在线视频区 | 欧美成人免费看片一区 | 久久er热这里只有精品免费 | 久久久在线视频精品免费观看 | 成人禁啪啪网站 | 国产日本高清动作片www网站 | 欧美福利一区二区三区 | 印度xxxxbbbb视频| 国产制服丝袜视频 | 国产a一级毛片午夜剧院 | ccmm123在线播放 | www一级黄色片 | 欧美aaaa黄色一级毛片 | 天天在线天天看成人免费视频 | 国产成人在线观看免费网站 | 国产亚洲精品久久久久久久 | 国产精品秒播无毒不卡 | 麻豆国产精品有码在线观看 | 91视频网址入口 | 国产乱子精品免费视观看片 |