python的描述器descriptor詳解
基本說明
Python的描述器(descriptor)是一種Python對(duì)象,可以通過定義一組特定的方法來管理另一個(gè)對(duì)象的訪問。描述器可以用于控制屬性的讀取、寫入和刪除等操作,同時(shí)還可以用于實(shí)現(xiàn)計(jì)算屬性、類屬性、屬性別名等高級(jí)功能。
在Python中,描述器是通過實(shí)現(xiàn)__get__()、__set__()和__delete__()方法的對(duì)象來定義的。當(dāng)一個(gè)描述器被綁定到一個(gè)類的屬性上時(shí),Python會(huì)自動(dòng)將其轉(zhuǎn)化為描述器對(duì)象,并在訪問該屬性時(shí)調(diào)用對(duì)應(yīng)的描述器方法。
class Descriptor:
def __get__(self, instance, owner):
print("Getting the value")
return self.value
def __set__(self, instance, value):
print("Setting the value")
self.value = value
class MyClass:
attr = Descriptor()
obj = MyClass()
obj.attr = 42
print(obj.attr)描述器是一種強(qiáng)大的Python語言特性,可以用于實(shí)現(xiàn)各種高級(jí)功能,例如:
- 計(jì)算屬性:描述器可以根據(jù)其他屬性的值動(dòng)態(tài)計(jì)算出一個(gè)屬性的值,而不是存儲(chǔ)屬性的值。這可以幫助我們簡化代碼,并且可以在不改變接口的情況下改變屬性的實(shí)現(xiàn)方式。
- 類屬性:描述器可以讓我們將屬性綁定到類上,而不是綁定到實(shí)例上。這可以讓我們?cè)谒袑?shí)例之間共享屬性值,并且可以在運(yùn)行時(shí)動(dòng)態(tài)更改屬性的值。
- 屬性別名:描述器可以讓我們定義一個(gè)屬性的別名,讓一個(gè)屬性具有多個(gè)名稱。這可以幫助我們簡化代碼,并且可以在不改變接口的情況下更改屬性的名稱。
- 數(shù)據(jù)驗(yàn)證:描述器可以讓我們?cè)谠O(shè)置屬性值之前驗(yàn)證輸入數(shù)據(jù),確保它們符合我們的預(yù)期格式和類型。這可以提高代碼的健壯性,并且可以幫助我們避免一些常見的錯(cuò)誤。
示例Demo1
假設(shè)我們有一個(gè)Temperature類,用于表示溫度。該類有一個(gè)名為celsius的屬性,表示攝氏溫度。我們希望實(shí)現(xiàn)以下功能:
- 計(jì)算屬性
fahrenheit,表示華氏溫度,它應(yīng)該是一個(gè)只讀屬性,可以通過攝氏溫度自動(dòng)計(jì)算得出。 - 限制
celsius屬性的取值范圍在-273.15℃到1000℃之間,如果嘗試設(shè)置超出此范圍的值,應(yīng)該引發(fā)ValueError異常。
下面是使用描述器實(shí)現(xiàn)以上功能的示例代碼:
class Celsius:
def __init__(self, value=0.0):
self._value = value
def __get__(self, instance, owner):
return self._value
def __set__(self, instance, value):
if value < -273.15 or value > 1000.0:
raise ValueError("Temperature out of range")
self._value = value
class Temperature:
celsius = Celsius()
@property
def fahrenheit(self):
return self.celsius * 1.8 + 32在這個(gè)示例中,我們定義了一個(gè)Celsius類,它是一個(gè)描述器,用于限制Temperature類的celsius屬性的取值范圍。在Celsius類中,我們實(shí)現(xiàn)了__get__()和__set__()方法,分別在讀取和設(shè)置celsius屬性時(shí)被調(diào)用。在__set__()方法中,我們檢查輸入的值是否在允許的范圍內(nèi),如果不是,則引發(fā)一個(gè)異常。
然后,我們定義了一個(gè)Temperature類,它有一個(gè)celsius屬性,它被綁定到Celsius類的實(shí)例上。我們還定義了一個(gè)只讀屬性fahrenheit,它可以通過celsius屬性自動(dòng)計(jì)算得出。
現(xiàn)在,我們可以創(chuàng)建一個(gè)Temperature對(duì)象,并設(shè)置其celsius屬性,如下所示:
t = Temperature() t.celsius = 25.0 print(t.celsius) # 輸出 25.0 print(t.fahrenheit) # 輸出 77.0
在這個(gè)示例中,我們創(chuàng)建了一個(gè)Temperature對(duì)象,并將其celsius屬性設(shè)置為25.0。然后,我們打印了celsius和fahrenheit屬性的值,它們分別是25.0和77.0,符合我們預(yù)期的結(jié)果。
如果我們嘗試設(shè)置一個(gè)超出允許范圍的值,例如-300.0,會(huì)引發(fā)一個(gè)異常,如下所示:
t.celsius = -300.0 # 引發(fā) ValueError: Temperature out of range
示例Demo2
下面是一個(gè)示例,演示如何使用描述器將屬性綁定到類上。
假設(shè)我們有一個(gè)名為Counter的類,它用于計(jì)數(shù)器操作,可以用于記錄創(chuàng)建的對(duì)象數(shù)或者其他類級(jí)別的計(jì)數(shù)。
我們可以使用描述器將計(jì)數(shù)器屬性綁定到Counter類上,如下所示:
class Counter:
_count = 0
class CountDescriptor:
def __get__(self, instance, owner):
return owner._count
def __set__(self, instance, value):
owner = type(instance)
owner._count = value
count = CountDescriptor()
def __init__(self):
type(self)._count += 1在這個(gè)示例中,我們定義了一個(gè)Counter類,它有一個(gè)名為count的屬性,它被綁定到了一個(gè)內(nèi)部的CountDescriptor描述器類上。
在CountDescriptor類中,我們實(shí)現(xiàn)了__get__()和__set__()方法,它們分別在讀取和設(shè)置count屬性時(shí)被調(diào)用。在__get__()方法中,我們返回Counter類的_count屬性的值。在__set__()方法中,我們通過獲取實(shí)例的類型(即Counter類),并設(shè)置其_count屬性的值來設(shè)置計(jì)數(shù)器的值。
然后,在Counter類的__init__()方法中,我們?cè)趧?chuàng)建對(duì)象時(shí)自動(dòng)增加計(jì)數(shù)器的值。
現(xiàn)在,我們可以創(chuàng)建多個(gè)Counter對(duì)象,并訪問它們的count屬性,如下所示:
c1 = Counter() c2 = Counter() print(c1.count) # 輸出 2 print(c2.count) # 輸出 2 Counter.count = 100 print(c1.count) # 輸出 100 print(c2.count) # 輸出 100
在這個(gè)示例中,我們創(chuàng)建了兩個(gè)Counter對(duì)象,并分別將它們存儲(chǔ)在c1和c2變量中。然后,我們打印它們的count屬性,它們的值都是2,表示我們已經(jīng)創(chuàng)建了兩個(gè)Counter對(duì)象。
接著,我們將Counter類的count屬性設(shè)置為100,這將改變所有對(duì)象的計(jì)數(shù)器值。然后,我們?cè)俅未蛴?code>c1和c2的count屬性,它們的值都變成了100,說明我們成功地將屬性綁定到了類上,實(shí)現(xiàn)了所有實(shí)例之間共享屬性值的效果。
示例Demo3
下面是一個(gè)示例,演示如何使用描述器實(shí)現(xiàn)屬性別名。
假設(shè)我們有一個(gè)Person類,它有一個(gè)名為name的屬性,表示人的名字?,F(xiàn)在我們想要為name屬性定義一個(gè)別名,叫做full_name,以便在某些情況下,我們可以使用full_name屬性來代替name屬性。
我們可以使用描述器來實(shí)現(xiàn)這個(gè)功能,如下所示:
class NameAlias:
def __init__(self, name):
self.name = name
def __get__(self, instance, owner):
return getattr(instance, self.name)
def __set__(self, instance, value):
setattr(instance, self.name, value)
class Person:
def __init__(self, name):
self.name = name
full_name = NameAlias("name")在這個(gè)示例中,我們定義了一個(gè)NameAlias描述器類,它用于實(shí)現(xiàn)屬性別名功能。在NameAlias類中,我們實(shí)現(xiàn)了__get__()和__set__()方法,它們分別在讀取和設(shè)置full_name屬性時(shí)被調(diào)用。在__get__()方法中,我們使用getattr()函數(shù)獲取對(duì)象的name屬性值,并返回它。在__set__()方法中,我們使用setattr()函數(shù)設(shè)置對(duì)象的name屬性值為傳入的值。
然后,在Person類中,我們定義了一個(gè)名為full_name的屬性,它被綁定到了一個(gè)NameAlias實(shí)例上,用于實(shí)現(xiàn)屬性別名。在Person類的__init__()方法中,我們初始化name屬性的值。現(xiàn)在,我們可以創(chuàng)建一個(gè)Person對(duì)象,并訪問它的name和full_name屬性,如下所示:
p = Person("Alice")
print(p.name) # 輸出 "Alice"
print(p.full_name) # 輸出 "Alice"
p.full_name = "Bob"
print(p.name) # 輸出 "Bob"
print(p.full_name) # 輸出 "Bob"在這個(gè)示例中,我們創(chuàng)建了一個(gè)Person對(duì)象,并將其存儲(chǔ)在p變量中。然后,我們打印它的name和full_name屬性,它們的值都是"Alice",表示它們是等價(jià)的。接著,我們將p的full_name屬性設(shè)置為"Bob",這將同時(shí)改變name屬性的值。然后,我們?cè)俅未蛴?code>name和full_name屬性,它們的值都是"Bob",說明我們成功地實(shí)現(xiàn)了屬性別名的功能。
示例Demo4
下面是一個(gè)示例,演示如何使用描述器實(shí)現(xiàn)數(shù)據(jù)驗(yàn)證功能。
假設(shè)我們有一個(gè)Person類,它有一個(gè)名為age的屬性,表示人的年齡。現(xiàn)在我們想要對(duì)age屬性的輸入數(shù)據(jù)進(jìn)行驗(yàn)證,以確保它的值在0到150之間。
我們可以使用描述器來實(shí)現(xiàn)這個(gè)功能,如下所示:
class AgeValidator:
def __get__(self, instance, owner):
return instance._age
def __set__(self, instance, value):
if not isinstance(value, int) or value < 0 or value > 150:
raise ValueError("Invalid age")
instance._age = value
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
age = AgeValidator()在這個(gè)示例中,我們定義了一個(gè)AgeValidator描述器類,它用于實(shí)現(xiàn)數(shù)據(jù)驗(yàn)證功能。在AgeValidator類中,我們實(shí)現(xiàn)了__get__()和__set__()方法,它們分別在讀取和設(shè)置age屬性時(shí)被調(diào)用。在__set__()方法中,我們首先檢查輸入的值是否是整數(shù),并且是否在0到150之間。如果輸入數(shù)據(jù)不符合要求,我們將引發(fā)一個(gè)ValueError異常。否則,我們將設(shè)置instance._age屬性的值為傳入的值。
然后,在Person類中,我們定義了一個(gè)名為age的屬性,它被綁定到了一個(gè)AgeValidator實(shí)例上,用于實(shí)現(xiàn)數(shù)據(jù)驗(yàn)證功能。在Person類的__init__()方法中,我們初始化name和age屬性的值。現(xiàn)在,我們可以創(chuàng)建一個(gè)Person對(duì)象,并嘗試設(shè)置其age屬性的值,如下所示:
p = Person("Alice", 25)
print(p.age) # 輸出 25
p.age = 200 # 引發(fā) ValueError: Invalid age在這個(gè)示例中,我們創(chuàng)建了一個(gè)Person對(duì)象,并將其存儲(chǔ)在p變量中。然后,我們打印它的age屬性,它的值是25。接著,我們嘗試將p的age屬性設(shè)置為200,這將引發(fā)一個(gè)ValueError異常,因?yàn)?00超出了允許的范圍。這說明我們成功地使用描述器實(shí)現(xiàn)了數(shù)據(jù)驗(yàn)證功能,可以避免一些常見的錯(cuò)誤。
在這個(gè)示例中,Person類中有兩個(gè)名為age的定義,一個(gè)是在__init__()方法中進(jìn)行初始化的實(shí)例屬性,另一個(gè)是通過描述器AgeValidator綁定到Person類上的類屬性。
當(dāng)我們?cè)?code>__init__()方法中設(shè)置self.age = age時(shí),它實(shí)際上是在創(chuàng)建一個(gè)實(shí)例屬性,而不是類屬性。這個(gè)實(shí)例屬性只對(duì)當(dāng)前對(duì)象有效,而不對(duì)所有Person對(duì)象都有效。
而當(dāng)我們?cè)?code>Person類中定義了一個(gè)名為age的描述器屬性時(shí),它實(shí)際上是在創(chuàng)建一個(gè)類屬性,這個(gè)屬性可以被所有Person對(duì)象所共享,它定義了age屬性的讀寫行為,可以對(duì)屬性值進(jìn)行驗(yàn)證,保證數(shù)據(jù)的正確性。
因此,這兩個(gè)age屬性雖然名字相同,但它們的作用范圍和用途不同。實(shí)例屬性是用來存儲(chǔ)每個(gè)對(duì)象的不同數(shù)據(jù),而類屬性則是用來實(shí)現(xiàn)屬性的共享和控制。描述器可以讓我們將類屬性綁定到一個(gè)自定義的屬性讀寫行為上,從而實(shí)現(xiàn)更靈活的屬性操作。
到此這篇關(guān)于python的描述器descriptor詳解的文章就介紹到這了,更多相關(guān)python的描述器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python?matplotlib?繪制散點(diǎn)圖詳解建議收藏
在數(shù)據(jù)統(tǒng)計(jì)圖表中,有一種圖表是散列點(diǎn)分布在坐標(biāo)中,反應(yīng)數(shù)據(jù)隨著自變量變化的趨勢(shì)。這篇文章主要介紹了如何通過matplotlib繪制散點(diǎn)圖,需要的朋友可以參考一下2021-12-12
python list使用示例 list中找連續(xù)的數(shù)字
這篇文章主要介紹了list中找連續(xù)的數(shù)字的示例,大家參考使用吧2014-01-01
Python?Numpy布爾數(shù)組在數(shù)據(jù)分析中的應(yīng)用小結(jié)
本文深入探討了Python的Numpy庫中的布爾數(shù)組功能,介紹了布爾運(yùn)算、布爾索引的使用方法,并通過示例展示了如何在數(shù)據(jù)分析中利用布爾數(shù)組進(jìn)行數(shù)據(jù)篩選和處理,感興趣的朋友一起看看吧2024-09-09
Python實(shí)現(xiàn)將DOC文檔轉(zhuǎn)換為PDF的方法
這篇文章主要介紹了Python實(shí)現(xiàn)將DOC文檔轉(zhuǎn)換為PDF的方法,涉及Python調(diào)用系統(tǒng)win32com組件實(shí)現(xiàn)文件格式轉(zhuǎn)換的相關(guān)技巧,需要的朋友可以參考下2015-07-07

