日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

python 属性描述符_Python属性描述符(二)

發(fā)布時間:2023/12/10 python 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 python 属性描述符_Python属性描述符(二) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

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)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。