屬性訪問控制
所謂的屬性訪問控制就是控制點號訪問屬性的行為,而且不僅是類的外部,連類的內部也受控制,代碼見真章,邊看代碼邊解釋:
?__getattr__(self, item)
定義當訪問 不存在 的屬性時的行為,注意是 不存在的屬性。
class Foo(object): def __init__(self, value): self.value = value def __getattr__(self, item): print item # 查看得到的參數是什么 print type(item) # 參數的類型是什么 return 'attr:%s' % item # 最后返回一個東西看其行為如何 a = Foo('scolia') # 創建一個實例
測試:
print a.value print type(a.value)
其行為和沒定義前正常,下面看看訪問一個不存在的屬性時會發生什么:
print a.abc
按照平常的情況,訪問不存在的屬性時肯定會拋出異常,但是這里輸出了三行,前兩個是方法輸出的,最后一行是外部的print語句輸出的,輸出的是方法的return值。方法得到的是我們訪問的屬性名,而且是以字符串的形式。
知道了以上信息后,我們就可以定制更多:
class Foo(object): def __init__(self, value): self.value = value def __getattr__(self, item): if item == 'scolia': return 'can not set attr: %s' % item # 訪問不存在的scolia屬性時,打印一句話而不報錯 else: raise AttributeError('not attr name: %s' % item) # 訪問其他不存在的屬性時,觸發異常。
測試:
a = Foo(123) print a.value # 訪問存在的屬性
print a.scolia # 訪問不存在的屬性,但我們做了特殊處理的
沒有觸發異常,和我們設想的一樣。
print a.good # 訪問不存在的屬性,但應該觸發異常的
觸發了我們想要的異常。
這里要再強調一遍,必須是訪問不存在的屬性時,才會調用這個方法,例如:
a.scolia = 321 print a.scolia
因為這個屬性 已經存在 了(我們手動添加了),所以訪問它的時候并沒有調用這個方法,而在方法里所做的任何處理,也不會有效。
更高級的技巧:
class Foo(object): def __init__(self, value, defulat=None): self.value = value self.__defulat = defulat def __getattr__(self, item): item = item.lower() # 用字符串的方法對其進行小寫 if item in self.__dict__: return self.__dict__[item] # 返回相應的屬性 else: self.__dict__[item] = self.__defulat # 若屬性不存在則添加這個屬性并使用默認值 return self.__dict__[item] a = Foo(123) a.scolia = 321 print a.SCOlia print a.good
我們實現了屬性的不區分大小寫訪問和自動添加不存在的屬性。
這里的秘訣在于活用 __dict__ 這個屬性,我在類的屬性中已經討論過這個屬性。這個屬性由python自動創建,是一個字典,包含對象的所有屬性,字典里的鍵就是屬性名,對應的值就是屬性值。所以這里在這個字典中添加了鍵和值,就相當于為對象添加了屬性和屬性值。
?__setattr__(self, key, value)
定義了設置屬性時的行為,包括在 __init__ 初始化函數中的設置行為:
class Foo(object): def __init__(self, value, defulat=None): self.value = value def __setattr__(self, key, value): print key, type(key) print value, type(value) a = Foo('scolia') b = Foo(123)
這里可以看到初始化函數中的屬性添加的行為也受到了控制,其中 key 得到的是屬性名,以字符串的形式;而 value 得到的是屬性值,屬性值根據輸入的不同而不同。
在這里,我們僅僅只是打印了幾句話,而沒有進行屬性的添加,所以當我們試圖訪問相應的屬性時,會發現根本就沒有:
print a.value
觸發了異常,表示沒有相應的屬性。
知道了這些之后我們可以做很多事情,例如將所有的屬性名變成小寫或大寫,控制某些屬性名不能添加之類的,就不再舉例。不過,這里你總不可能用 self.key = value 來添加屬性吧,因為 key 始終是一個字符串。這個時候就要使用 __dict__ 屬性了,向這個字典中添加相應的鍵值對就可以了,具體就不再演示了。
?__delattr__(self, item)
定義了刪除一個屬性時的行為,item 得到的也是一個字符串形式的屬性名,具體細節也無序多說,只要 del 掉 __dict__ 字典中對應是鍵和值就行了。另外,刪除不存在的屬性時調用的也是這個方法。
?__getattribute__(self, name)
這個方法定義了所有屬性訪問的行為,注意是所有,而不是 __getattr__ 中的不存在。當實現了這個方法之后,將會覆蓋 __getattr__ 方法,畢竟所有涵蓋了不存在。
這個方法只在新式類中有效。
但是,你也可以顯式的調用 __getattr__,例如 a.__getattr__ 來使用這個被掩蓋的方法,或者是觸發 AttributeError 異常時也會自動調用它。
然而,非常不建議使用這個方法,因為可能會很多不可預知的異常情況,最常見的就是無盡的遞歸調用,例如:
class Foo(object): def __init__(self, value): self.value = value def __getattribute__(self, item): return self.__dict__[item] a = Foo('scolia') print a.value
這段代碼看起來很正常,但是這里有一個陷阱,因為類中的所有的屬性訪問都是受這幾個魔法方法控制的,包括上面介紹的幾個魔法方法。它們似乎比普通的魔法方法擁有更高的權限一般。
但這就導致了一個問題,例如這里的 self.__dict__[item] ,這句話也受屬性訪問的控制,盡管這個屬性是 python 為我們創建的。
也就是說獲取 self.__dict__ 時,會再次調用 __getattribute__ 方法,然后方法內又調用了 self.__dict__ 。這樣無限循環下去,最終會拋出一個異常。
異常信息非常長,這里我是拉到最后才截的圖。
其實不僅這個魔法方法會導致這樣異常,上面討論的幾種魔法方法可能都會出現這個問題,只不過這個魔法方法的權限更大,所以異常出現的可能性更高一些。
這也就是不推薦這個魔法方法的原因,而使用其他的屬性控制方法的時候也要小心。
而到目前為止,我們所學到的屬性訪問的方法只有兩種,一是直接用點號訪問,還有就是先通過點號訪問__dict__ 屬性,然后在這個字典中獲取相應的鍵值對。而這兩種方法都受到了 __getattribute__ 的控制,調用它們就相當于沒有終點的自調用(有終點的自調用有時能提升效率),那么這個方法到底要怎么用呢?
技巧就是調用父類的這個方法:
class Foo(object): def __init__(self, value): self.value = value def __getattribute__(self, item): return object.__getattribute__(self, item) # 非綁定方法要顯式傳遞self a = Foo('scolia') print a.value
這里調用的是object的這個方法,如果是涉及到繼承的話:
class Boo(Foo): def __init__(self, value): self.value = value def __getattribute__(self, item): return Foo.__getattribute__(self, item) # return super(Boo, self).__getattribute__(item) 也可以使用super函數讓python自動在其父類們尋找這個方法。 a = Foo('scolia') print a.value b = Boo(123) print b.value
訪問正常。
其實最后調用了還是 object 或其他內置類型的方法。
而我們姑且不起探究object到底是怎么實現的,因為這可能是用 C 所寫的,只要會用就可以,雖然這個方法用的也不多。
最后附上一個完整的例子:
class Foo(object): def __init__(self, value): self.value = value def __getattr__(self, item): if item == 'scolia': return 'no attr:%s' % item elif item in self.__dict__: return self.__dict__[item] else: raise AttributeError('no attr:%s' % item) def __setattr__(self, key, value): if key == 'good': print 'can not set the attr: good' else: self.__dict__[key] = value def __delattr__(self, item): if item == 'a': print 'no attr: good' else: del self.__dict__[item] def __getattribute__(self, item): if item == 'a': raise AttributeError('not a') return object.__getattribute__(self, item) a = Foo('scolia') print a.value # 正常訪問 a.a = 123 # __getattribute__會觸發AttributeError異常,此時調用__getattr__ # 而__getattr__添加了這個屬性,所以最后異常沒有觸發,屬性也添加了 print a.a # 結果能夠訪問 del a.a # 試圖刪除這個屬性 print a.a # 刪除行為被阻止了,所以該屬性還在 a.good = 'good' # 因為添加被阻止了 print a.good # 所以訪問失敗了
結果:
以上這篇python魔法方法-屬性訪問控制詳解就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支持腳本之家。
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
