python 属性描述符_Python属性描述符(二)
Python存取屬性的方式特別不對等,通過實例讀取屬性時,通常返回的是實例中定義的屬性,但如果實例未曾定義過該屬性,就會獲取類屬性,而為實例的屬性賦值時,通常會在實例中創(chuàng)建屬性,而不會影響到類本身。這種不對等的方式對描述符類也有影響。
def cls_name(obj_or_cls): # 傳入一個實例,返回類名
cls = type(obj_or_cls)
if cls is type:
cls = obj_or_cls
return cls.__name__.split('.')[-1]
def display(obj):
cls = type(obj)
if cls is type: # 如果obj是一個類,則進入該分支
return ''.format(obj.__name__)
elif cls in [type(None), int]: # 如果obj是None或者數(shù)值,進入該分支
return repr(obj)
else: # 如果obj是一個實例
return ''.format(cls_name(obj))
def print_args(name, *args):
pseudo_args = ', '.join(display(x) for x in args)
print('-> {}.__{}__({})'.format(cls_name(args[0]), name, pseudo_args))
class Overriding: # <1>
"""a.k.a. data descriptor or enforced descriptor"""
def __get__(self, instance, owner):
print_args('get', self, instance, owner)
def __set__(self, instance, value):
print_args('set', self, instance, value)
class OverridingNoGet: # <2>
"""an overriding descriptor without ``__get__``"""
def __set__(self, instance, value):
print_args('set', self, instance, value)
class NonOverriding: # <3>
"""a.k.a. non-data or shadowable descriptor"""
def __get__(self, instance, owner):
print_args('get', self, instance, owner)
class Managed: # <4>
over = Overriding()
over_no_get = OverridingNoGet()
non_over = NonOverriding()
def spam(self): # <5>
print('-> Managed.spam({})'.format(display(self)))
有__get__和__set__方法的典型覆蓋型描述符,在這個示例中,各個方法都調(diào)用了print_args()函數(shù)
沒有__get__方法的覆蓋型描述符
沒有__set__方法,所以這是非覆蓋型描述符
托管類,使用各個描述符類的一個實例
spam方法放這里是為了對比,因為方法也是描述符
覆蓋型描述符
實現(xiàn)__set__方法的描述符屬于覆蓋型描述符,雖然描述符是類屬性,但是實現(xiàn)了__set__方法的話,會覆蓋對實例屬性的賦值操作。特性也是覆蓋型描述符,見Python動態(tài)屬性和特性(二),如果沒有提供設值函數(shù),property類中的fset方法就會拋出AttributeError異常,指明那個屬性時只讀的
下面我們來看下覆蓋型描述符的行為:
>>> obj = Managed() # <1>
>>> obj.over # <2>
-> Overriding.__get__(, , )
>>> Managed.over # <3>
-> Overriding.__get__(, None, )
>>> obj.over = 8 # <4>
-> Overriding.__set__(, , 8)
>>> obj.over # <5>
-> Overriding.__get__(, , )
>>> obj.__dict__["over"] = 9 # <6>
>>> vars(obj) # <7>
{'over': 9}
>>> obj.over # <8>
-> Overriding.__get__(, , )
創(chuàng)建托管實例Managed對象
obj.over觸發(fā)描述符的__get__方法,__get__方法第一個參數(shù)是描述符實例,第二個參數(shù)的值是托管實例obj,第三個參數(shù)是托管類對象
Managed.over 觸發(fā)描述符的__get__方法,第二個參數(shù)(instance)的值是 None,第一個和第三個同上
為obj.over賦值,觸發(fā)描述符的__set__方法,最后一個參數(shù)的值是8
讀取 obj.over,仍會觸發(fā)描述符的__get__方法
跳過描述符,直接通過obj.__dict__屬性設值
確認值在obj.__dict__屬性中,在over鍵名下
然而,即使是名為over的實例屬性,Managed.over描述符仍會覆蓋讀取 obj.over 這個操作
沒有__get__方法的覆蓋型描述符
>>> obj.over_no_get # <1>
>>> Managed.over_no_get # <2>
>>> obj.over_no_get = 8 # <3>
-> OverridingNoGet.__set__(, , 8)
>>> obj.over_no_get # <4>
>>> obj.__dict__['over_no_get'] = 6 # <5>
>>> obj.over_no_get # <6>
6
>>> obj.over_no_get = 7 # <7>
-> OverridingNoGet.__set__(, , 7)
>>> obj.over_no_get # <8>
6
描述符實例沒有__get__方法,所以obj.over_no_get從類中獲取描述符實例
直接從托管類中讀取over_no_get屬性,也就是描述符實例
為obj.over_no_get賦值hui會觸發(fā)描述符類的__set__方法
因為__set__方法沒有修改屬性,所以在此讀取obj.over_no_get獲取的仍然是托管類中的描述符實例
通過實例的__dict__屬性設置名為over_no_get的實例屬性
現(xiàn)在,over_no_get實例屬性會覆蓋描述符實例
為obj.over_no_get實例屬性賦值,仍然會經(jīng)過__set__方法
讀取時,只要有同名的實例屬性,描述符實例就會被覆蓋
非覆蓋型描述符:沒有實現(xiàn)__set__方法的描述符稱為是非覆蓋型描述符,如果設置了同名的實例屬性,描述符會被覆蓋,致使描述符無法處理那個實例的那個屬性
>>> obj.non_over # <1>
-> NonOverriding.__get__(, , )
>>> obj.non_over = 6 # <2>
>>> obj.non_over # <3>
6
>>> Managed.non_over # <4>
-> NonOverriding.__get__(, None, )
>>> del obj.non_over # <5>
>>> obj.non_over # <6>
-> NonOverriding.__get__(, , )
obj.non_over觸發(fā)描述符的__get__方法,第二個參數(shù)的值是obj
Managed.non_over是非覆蓋型描述符,因此沒有干涉賦值操作的__set__方法
obj有個名為non_over的實例屬性,把Managed類的同名描述符屬性遮蓋掉
Managed.non_over描述符依然存在,會通過類截獲這次訪問
刪除non_over實例屬性
讀取obj.non_over時,會觸發(fā)類中描述符的__get__方法
覆蓋類中的描述符:不管描述符是不是覆蓋型的,為類屬性賦值都能覆蓋描述符
>>> obj = Managed() # <1>
>>> Managed.over = 1 # <2>
>>> Managed.over_no_get = 2
>>> Managed.non_over = 3
>>> obj.over, obj.over_no_get, obj.non_over # <3>
(1, 2, 3)
新建一個Managed實例
覆蓋類中的描述符屬性
通過實例訪問描述符屬性,新的值覆蓋描述符
方法是描述符:在類中定義的函數(shù)屬于綁定方法,用戶定義的函數(shù)都有__get__方法,所以依附到類上時,就相當于描述符,方法是非覆蓋型描述符
>>> obj = Managed()
>>> obj.spam # <1>
>
>>> Managed.spam # <2>
>>> obj.spam = 7 # <3>
>>> obj.spam
7
obj.spam獲取的是綁定方法對象
Managed.spam獲取的是函數(shù)
如果為obj.spam賦值,會遮蓋類屬性,導致無法通過obj實例訪問spam方法
函數(shù)沒有__set__方法,因此是非覆蓋型描述符,從上面的例子來看,obj.spam和Managed.spam獲取的是不同對象。與描述符一樣,通過托管類訪問時,函數(shù)__get__方法會返回自身的引用,但是通過實例訪問時,函數(shù)的__get__方法返回的是綁定方法對象:一種可調(diào)用的對象,里面包裝著函數(shù),并把托管實例(如obj)綁定給函數(shù)的第一個參數(shù)(即self),這與functools.partial函數(shù)的行為一致
為了了解這種機制,讓我們看下面一個例子:
import collections
class Text(collections.UserString):
def __repr__(self):
return 'Text({!r})'.format(self.data)
def reverse(self):
return self[::-1]
測試Text類:
>>> word = Text("forward")
>>> word # <1>
Text('forward')
>>> word.reverse() # <2>
Text('drawrof')
>>> Text.reverse(Text('backward')) # <3>
Text('drawkcab')
>>> type(Text.reverse), type(word.reverse) # <4>
(, )
>>> list(map(Text.reverse, ['repaid', (10, 20, 30), Text('stressed')])) # <5>
['diaper', (30, 20, 10), Text('desserts')]
>>> func = Text.reverse.__get__(word) # <6>
>>> func() # <7>
Text('drawrof')
>>> Text.reverse.__get__(None, Text) # <8>
>>> Text.reverse
>>> word.reverse # <9>
>>> Text.reverse.__get__(word)
>>> word.reverse.__self__ # <10>
Text('forward')
>>> word.reverse.__func__ is Text.reverse # <11>
True
Text實例的repr方法返回一個類似Text構造方法調(diào)用的字符串,可用于創(chuàng)建相同的實例
reverse方法返回反向拼寫的單詞
在類上調(diào)用方法并傳入一個實例相當于調(diào)用實例的函數(shù)
從類獲取方法和從實例獲取方法的類型是不同的,一個是function,一個是method
Text.reverse相當于函數(shù),甚至可以處理Text實例之外的其他對象
函數(shù)都是非覆蓋型描述符。在函數(shù)上調(diào)用__get__方法時傳入實例,得到的是綁定到那個實例上的方法
我們執(zhí)行第六個步驟得到的func對象,與在實例上調(diào)用函數(shù)效果一樣
調(diào)用函數(shù)的__get__方法時,如果instance 參數(shù)的值是None,那么得到的是函數(shù)本身
word.reverse表達式其實會調(diào)用Text.reverse.__get__(word),返回對應的綁定方法
綁定方法對象有個__self__屬性,其值是調(diào)用這個方法的實例引用
綁定方法的__func__屬性是依附在托管類上那個原始函數(shù)的引用
綁定方法對象還有個__call__方法,用于處理真正的調(diào)用過程,這個方法調(diào)用__func__屬性引用的原始函數(shù),把函數(shù)的第一個參數(shù)設置為綁定方法的__self__屬性,這就是形參self的隱式綁定方式
描述符用法建議:
使用特性以保持簡單:內(nèi)置的property類創(chuàng)建的其實是覆蓋型描述符,__set__方法和__get__方法都實現(xiàn)了,即便不定義設值方法也是如此。特性的__set__方法默認拋出AttributeError: can't set attribute異常,因此創(chuàng)建只讀屬性最簡單的方式是使用特性
只讀描述符必須有__set__方法:如果使用描述符類實現(xiàn)只讀屬性,__get__和__set__兩個方法都必須定義,否則實例的同名屬性會遮蓋住描述符,只讀屬性的__set__方法只需要拋出AttributeError異常,并提供合適的錯誤信息
用于驗證的描述符可以只有__set__方法:對僅用于驗證的描述符來說,__set__方法應該檢查value參數(shù)獲得的值,如果有效,使用描述符實例的名稱為鍵,直接在實例的__dict__ 屬性中設置。這樣,從實例中讀取同名屬性的速度很快,因為不用經(jīng)過 __get__方法處理
僅有__get__方法的描述符可以實現(xiàn)高效緩存:如果只編寫了__get__方法,那么創(chuàng)建的是非覆蓋型描述符。這種描述符可用于執(zhí)行某些耗費資源的計算,然后為實例設置同名屬性,緩存結果。 同名實例屬性會遮蓋描述符,因此后續(xù)訪問會直接從實例的__dict__ 屬性中獲取值,而不會再觸發(fā)描述符的__get__方法
非特殊的方法可以被實例屬性遮蓋:由于函數(shù)和方法只實現(xiàn)了__get__方法,它們不會處理同名實例屬性的賦值操作。因此,像 my_obj.the_method = 7這樣簡單賦值之后, 后續(xù)通過該實例訪問the_method得到的是數(shù)字7——但是不影響類或其他實例。然而,特殊方法不受這個問題的影響。解釋器只會在類中尋找特殊的方法,也就是說,repr(x)執(zhí)行的其實是x.__class__.__repr__(x),因此x的__repr__屬性對repr(x)方法調(diào)用沒有影響。出于同樣的原因,實例的_getattr__屬性不會破壞常規(guī)的屬性訪問規(guī)則
總結
以上是生活随笔為你收集整理的python 属性描述符_Python属性描述符(二)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 股神巴菲特炒股的技巧,三招至关重要
- 下一篇: python json.dumps慢_p