詳解Python中的Descriptor描述符類
描述符是調(diào)和屬性訪問的一個(gè)類。描述符類可用來獲取、設(shè)置或刪除屬性值。描述符對(duì)象是在類定義的時(shí)候構(gòu)建在一個(gè)類中的。
一般來說,描述符是一個(gè)具有綁定行為的對(duì)象屬性,其屬性的訪問被描述符協(xié)議方法覆寫。這些方法是__get__()、 __set__()和__delete__(),一個(gè)對(duì)象中只要包含了這三個(gè)方法(譯者注:包含至少一個(gè)),就稱它為描述符。
屬性訪問的默認(rèn)行為是從一個(gè)對(duì)象的字典中獲取 (get)、設(shè)置 (set)、刪除 (delete) 屬性。例如:a.x 的查找鏈?zhǔn)加?a.__dict__['x'],然后是 type(a).__dict__['x'],然后是 type(a) 除元類之外的基類(譯者注:如果繼承樹很深,可能會(huì)訪問多個(gè)基類)。如果查找到的值是包含一個(gè)描述符方法的對(duì)象,那么Python可能會(huì)重寫(該對(duì)象)的默認(rèn)行為并調(diào)用那個(gè)描述符方法。注意只有在新式對(duì)象或者新式類(繼承自object或者type)中描述符才會(huì)被調(diào)用。
描述符是一個(gè)功能強(qiáng)大、通用的協(xié)議。它們是屬性、方法、靜態(tài)方法、類方法、super()背后的實(shí)現(xiàn)機(jī)制。它們被廣泛使用于Python 2.2中用來實(shí)現(xiàn)新式類。描述符簡化了底層的C代碼并為Python編程提供了一套靈活的新工具。
描述符設(shè)計(jì)模式有兩個(gè)部分:一個(gè)所有者類和屬性描述符本身。所有者類給它的屬性使用一個(gè)或多個(gè)描述符。描述符類定義了獲取、設(shè)置和刪除方法的組合。描述符類的一個(gè)實(shí)例將會(huì)是所有者類的一個(gè)屬性。
特性是基于所有者類的方法函數(shù)。描述符不像特性,是一個(gè)類的實(shí)例,與所有者類不同。因此,描述符通常是可重用的通用屬性。所有者類可以有多個(gè)不同描述符類的實(shí)例來類管理具有相似行為的屬性。
不像其他屬性,描述符在類級(jí)別上創(chuàng)建。它們不是在__init()__初始化時(shí)創(chuàng)建。然而描述符的值可以在初始化期間設(shè)置,描述符通常是作為類的一部分,在任何方法函數(shù)之外來構(gòu)建的。
當(dāng)所有者類被定義時(shí),每個(gè)描述符對(duì)象都是被綁定到一個(gè)不同的類級(jí)別屬性的描述符類實(shí)例。
被確認(rèn)為一個(gè)描述符,一個(gè)類必須實(shí)現(xiàn)以下三個(gè)方法的任意組合。
- Descriptor.__get__(self, instance, owner) -> object:在這個(gè)方法中,instance參數(shù)是即將被訪問的對(duì)象的self變量。owner參數(shù)是所有者類的對(duì)象。如果這個(gè)描述符在類的上下文中被調(diào)用,instance參數(shù)將得到一個(gè)None值。這必須返回描述符的值。
- Descriptor.__set__(self, instance, value):在這個(gè)方法中,instance參數(shù)是即將被訪問的對(duì)象的self變量。value參數(shù)是描述符需要設(shè)置的新值。
- Descriptor.__delete__(self, instance)在這個(gè)方法中,instance參數(shù)是即將被訪問的對(duì)象的self變量。該描述符的方法必須刪除這個(gè)屬性的值。
有時(shí),一個(gè)描述符類還將需要一個(gè)__init__()方法函數(shù)來初始化描述符的內(nèi)部狀態(tài)。
有兩種基于已定義方法的描述符,如下所示:
1.非數(shù)據(jù)描述符:這種描述符定義__set__()或__delete__()或兩者皆有。它不能定義__get__()。非數(shù)據(jù)描述符對(duì)象往往會(huì)被用作表達(dá)式的一部分。它可能是一個(gè)可調(diào)用對(duì)象,或者它可能有自己的屬性或方法。一個(gè)不可變的非數(shù)據(jù)描述符必須實(shí)現(xiàn)__set__(),但可能只是拋出AttributeError。這些描述符設(shè)計(jì)時(shí)很簡單,因?yàn)榻涌诟`活。
2.數(shù)據(jù)描述符:這種描述符至少定義__get__()。通常,它定義__get__()和__set__()來創(chuàng)建一個(gè)可變對(duì)象。鑒于描述符將在很大程度上是不可見的,則不能更進(jìn)一步的再定義屬性或方法。屬性的引用有一個(gè)數(shù)據(jù)描述符的數(shù)據(jù)被委托給描述符的__get__()、__set__()或__delete__()方法。這些是很難設(shè)計(jì)的,所以我們稍后來再看。
描述符有各種各樣的用例。在內(nèi)部,Python使用描述符有以下幾個(gè)原因:
- 在隱藏的內(nèi)部,類的方法是作為描述符來實(shí)現(xiàn)。這些非數(shù)據(jù)描述符應(yīng)用方法函數(shù)到對(duì)象以及不同的參數(shù)值。
- property()函數(shù)通過給一個(gè)字段創(chuàng)建數(shù)據(jù)描述符來實(shí)現(xiàn)。
- 一個(gè)類方法或靜態(tài)方法被實(shí)現(xiàn)為一個(gè)描述符;這被應(yīng)用到類中來代替類的實(shí)例。
當(dāng)我們考慮一個(gè)描述符的目的,我們還必須為數(shù)據(jù)作為描述符可以正常工作來考察三種常見用例,如下所示:
- 描述符對(duì)象有數(shù)據(jù)或獲取到了數(shù)據(jù)。在這種情況下,描述符對(duì)象的self變量是有意義的且描述符是有狀態(tài)的。數(shù)據(jù)描述符的__get__()方法返回這個(gè)內(nèi)部數(shù)據(jù)。非數(shù)據(jù)描述符,描述符有其他方法或?qū)傩詠碓L問這些數(shù)據(jù)。
- 包含數(shù)據(jù)的所有者實(shí)例。在這種情況下,描述符對(duì)象必須使用instance參數(shù)來引用值到所有者對(duì)象中。數(shù)據(jù)描述符的__get__()方法從實(shí)例獲取數(shù)據(jù)。非數(shù)據(jù)描述符有其他方法訪問實(shí)例數(shù)據(jù)。
- 包含相關(guān)數(shù)據(jù)的所有者類。在這種情況下,描述符對(duì)象必須使用owner參數(shù)。這是常用的當(dāng)描述符實(shí)現(xiàn)了應(yīng)用于整個(gè)類的靜態(tài)方法或類方法。
我們將仔細(xì)看下第一種情況。我們看看創(chuàng)建帶有__get__()和__set__()方法的數(shù)據(jù)描述符。我們也會(huì)看看創(chuàng)建沒有__get__()方法的非數(shù)據(jù)描述符。
第二種情況(所有者實(shí)例中的數(shù)據(jù))展示了@property裝飾器都做了些什么??赡艿膬?yōu)勢(shì)是描述符有一個(gè)傳統(tǒng)的特性將計(jì)算從擁有者類移到描述符類中。這傾向于分片類設(shè)計(jì)且可能不是最好的方法。如果計(jì)算是真正史詩般的復(fù)雜,策略模式可能會(huì)更好。
第三種情況展示@staticmethod和@classmethod裝飾器是如何實(shí)現(xiàn)的。我們不需要重新發(fā)明輪子。
1、使用非數(shù)據(jù)描述符
我們經(jīng)常會(huì)有一些緊密綁定了屬性值的小對(duì)象。對(duì)于這個(gè)示例,我們將看看數(shù)值被綁定到單位的舉措。
下面是一個(gè)簡單的非數(shù)據(jù)描述符類,它缺少一個(gè)__get__()方法:
class UnitValue_1: """Measure and Unit combined.""" def __init__(self, unit): self.value = None self.unit = unit self.default_format = "5.2f" def __set__(self, instance, value): self.value = value def __str__(self): return "{value:{spec}} {unit}" .format(spec=self.default_format, **self.__dict__) def __format__(self, spec="5.2f"): #print( "formatting", spec ) if spec == "": spec = self.default_format return "{value:{spec}} {unit}".format(spec=spec, **self.__dict__)
這個(gè)類定義了一對(duì)簡單的值,一個(gè)可變的(值),另一個(gè)是有效的不可變對(duì)象(單位)。
當(dāng)這個(gè)描述符被訪問時(shí),描述符對(duì)象本身是可用的,且描述符的其他方法或?qū)傩钥梢员皇褂?。我們可以使用這個(gè)描述符來創(chuàng)建類去管理尺寸和其他與物理單位有關(guān)的數(shù)值。
下面是一個(gè)類,做速度-時(shí)間-距離的及早計(jì)算:
class RTD_1: rate = UnitValue_1("kt") time = UnitValue_1("hr") distance = UnitValue_1("nm") def __init__(self, rate=None, time=None, distance=None): if rate is None: self.time = time self.distance = distance self.rate = distance / time if time is None: self.rate = rate self.distance = distance self.time = distance / rate if distance is None: self.rate = rate self.time = time self.distance = rate * time def __str__(self): return "rate: {0.rate} time: {0.time} distance:{0.distance}".format(self)
一旦對(duì)象被創(chuàng)建且屬性被加載,丟失的值就已經(jīng)被計(jì)算。一旦計(jì)算,描述符可以檢查獲取值或單位的名稱。此外,描述符對(duì)str()有一個(gè)方便的響應(yīng)和請(qǐng)求格式。
下面是描述符和RTD_1類之間的交互:
>>> m1 = RTD_1(rate=5.8, distance=12) >>> str(m1) 'rate: 5.80 kt time: 2.07 hr distance: 12.00 nm' >>> print("Time:", m1.time.value, m1.time.unit) Time: 2.0689655172413794 hr
我們創(chuàng)建了一個(gè)帶有rate和distance參數(shù)的RTD_1實(shí)例。這些都是用來計(jì)算rate和distance描述符的__set__()方法。
當(dāng)我們請(qǐng)求str(m1),這會(huì)計(jì)算RTD_1的所有str()方法,轉(zhuǎn)而使用rate、time和distance描述符的__format__()方法。這為我們提供了數(shù)字和單位。
鑒于非數(shù)據(jù)描述符沒有__get__()且不返回其內(nèi)部值,我們可以訪問描述符的單個(gè)元素。
2、使用數(shù)據(jù)描述符
數(shù)據(jù)描述符設(shè)計(jì)要復(fù)雜一些,因?yàn)樗鼘?duì)接口有限制。它必須有一個(gè)__get__()方法,且只能有__set__()或__delete__()。這是所有的接口:這些方法從一到三,沒有其他方法。引入一個(gè)額外的方法意味著Python不會(huì)把該類當(dāng)作一個(gè)正確的數(shù)據(jù)描述符。
我們會(huì)使用描述符設(shè)計(jì)一個(gè)簡單的單位轉(zhuǎn)換模式,可以在__get__()和__set__()方法做適當(dāng)?shù)霓D(zhuǎn)換。
下面是一個(gè)單位描述符的超類,它在其他單位和標(biāo)準(zhǔn)單位之間做轉(zhuǎn)換:
class Unit: conversion = 1.0 def __get__(self, instance, owner): return instance.kph * self.conversion def __set__(self, instance, value): instance.kph = value / self.conversion
該類用簡單的乘法和除法將標(biāo)準(zhǔn)單位轉(zhuǎn)換為其他非標(biāo)準(zhǔn)單位,反之亦然。
通過這個(gè)超類,我們可以從一個(gè)標(biāo)準(zhǔn)單位定義一些轉(zhuǎn)換。在前面的示例,標(biāo)準(zhǔn)單位是千米時(shí)(公里/小時(shí))。
以下是這兩個(gè)轉(zhuǎn)換描述符
class Knots(Unit): conversion = 0.5399568 class MPH(Unit): conversion = 0.62137119
繼承方法非常有用。唯一改變的是轉(zhuǎn)換因子。這些類可用于處理涉及單位轉(zhuǎn)換的值。我們可以處理英里每小時(shí)或可交換的節(jié)點(diǎn)。下面是一個(gè)標(biāo)準(zhǔn)單位的單位描述符,公里每小時(shí):
class KPH(Unit): def __get__(self, instance, owner): return instance._kph def __set__(self, instance, value): instance._kph = value
這個(gè)類代表一個(gè)標(biāo)準(zhǔn),所以不做任何轉(zhuǎn)換。它使用一個(gè)私有變量實(shí)例保存速度千米每小時(shí)的標(biāo)準(zhǔn)值。避免任何算術(shù)轉(zhuǎn)換是一個(gè)簡單的技術(shù)優(yōu)化。避免任何一個(gè)公共字段的引用是至關(guān)重要的,來規(guī)避無限遞歸。
下面這個(gè)類,它對(duì)于一個(gè)給定的尺寸提供了一組轉(zhuǎn)換:
class Measurement: kph = KPH() knots = Knots() mph = MPH() def __init__(self, kph=None, mph=None, knots=None): if kph: self.kph = kph elif mph: self.mph = mph elif knots: self.knots = knots else: raise TypeError def __str__(self): return "rate: {0.kph} kph = {0.mph} mph = {0.knots} knots".format(self)
對(duì)于不同的單位每個(gè)類級(jí)別的屬性都是描述符。各種描述符的獲取和設(shè)置方法會(huì)做適當(dāng)?shù)霓D(zhuǎn)換。我們可以使用這個(gè)類在各種單位之間進(jìn)行速度轉(zhuǎn)換。
以下是與Measurement類交互的一個(gè)例子:
>>> m2 = Measurement(knots=5.9) >>> str(m2) 'rate: 10.92680006993152 kph = 6.789598762345432 mph = 5.9 knots' >>> m2.kph 10.92680006993152 >>> m2.mph 6.789598762345432
我們通過設(shè)置不同的描述符創(chuàng)建了一個(gè)Measurement類的對(duì)象。在第一個(gè)示例中,我們?cè)O(shè)置了節(jié)點(diǎn)描述符。
當(dāng)我們顯示的值是一個(gè)大字符串,則每個(gè)描述符的__get__()都將被使用。這些方法從所有者對(duì)象獲取內(nèi)部kph字段值,應(yīng)用一個(gè)轉(zhuǎn)換因子,且返回一個(gè)結(jié)果值。
kph字段還使用了一個(gè)描述符。這個(gè)描述符不做任何轉(zhuǎn)換;然而,它只是返回了緩存在所有者對(duì)象的私有值。KPH和Knots描述符要求所有者類實(shí)現(xiàn)一個(gè)kph屬性。
相關(guān)文章
Python模塊學(xué)習(xí)之subprocess詳解
subprocess是Python?2.4中新增的一個(gè)模塊,它允許你生成新的進(jìn)程,連接到它們的?input/output/error?管道,并獲取它們的返回(狀態(tài))碼,下面小編就來和大家聊聊它的具體使用吧2023-08-08Python標(biāo)準(zhǔn)庫datetime之datetime模塊用法分析詳解
這篇文章主要介紹了Python標(biāo)準(zhǔn)庫datetime之datetime模塊用法分析詳解,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-07-07python opencv檢測直線 cv2.HoughLinesP的實(shí)現(xiàn)
cv2.HoughLines()函數(shù)是在二值圖像中查找直線,本文結(jié)合示例詳細(xì)的介紹了cv2.HoughLinesP的用法,感興趣的可以了解一下2021-06-06python topk()函數(shù)求最大和最小值實(shí)例
這篇文章主要介紹了python topk()函數(shù)求最大和最小值實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-04-04