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í)例上。這可以讓我們在所有實(shí)例之間共享屬性值,并且可以在運(yùn)行時(shí)動(dòng)態(tài)更改屬性的值。
- 屬性別名:描述器可以讓我們定義一個(gè)屬性的別名,讓一個(gè)屬性具有多個(gè)名稱。這可以幫助我們簡化代碼,并且可以在不改變接口的情況下更改屬性的名稱。
- 數(shù)據(jù)驗(yàn)證:描述器可以讓我們在設(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__()
方法中,我們在創(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ù)器值。然后,我們再次打印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
屬性的值。然后,我們再次打印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
屬性的值?,F(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)我們在__init__()
方法中設(shè)置self.age = age
時(shí),它實(shí)際上是在創(chuàng)建一個(gè)實(shí)例屬性,而不是類屬性。這個(gè)實(shí)例屬性只對(duì)當(dāng)前對(duì)象有效,而不對(duì)所有Person
對(duì)象都有效。
而當(dāng)我們在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ù)隨著自變量變化的趨勢。這篇文章主要介紹了如何通過matplotlib繪制散點(diǎn)圖,需要的朋友可以參考一下2021-12-12python list使用示例 list中找連續(xù)的數(shù)字
這篇文章主要介紹了list中找連續(xù)的數(shù)字的示例,大家參考使用吧2014-01-01Python?Numpy布爾數(shù)組在數(shù)據(jù)分析中的應(yīng)用小結(jié)
本文深入探討了Python的Numpy庫中的布爾數(shù)組功能,介紹了布爾運(yùn)算、布爾索引的使用方法,并通過示例展示了如何在數(shù)據(jù)分析中利用布爾數(shù)組進(jìn)行數(shù)據(jù)篩選和處理,感興趣的朋友一起看看吧2024-09-09Python實(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