Python基礎(chǔ)詳解之描述符
一、描述符定義
描述符是一種類,我們把實(shí)現(xiàn)了__get__()、__set__()和__delete__()中的其中任意一種方法的類稱之為描述符。
描述符的作用是用來代理一個(gè)類的屬性,需要注意的是描述符不能定義在被使用類的構(gòu)造函數(shù)中,只能定義為類的屬性,它只屬于類的,不屬于實(shí)例,我們可以通過查看實(shí)例和類的字典來確認(rèn)這一點(diǎn)。
描述符是實(shí)現(xiàn)大部分Python類特性中最底層的數(shù)據(jù)結(jié)構(gòu)的實(shí)現(xiàn)手段,我們常使用的@classmethod、@staticmethd、@property、甚至是__slots__等屬性都是通過描述符來實(shí)現(xiàn)的。它是很多高級(jí)庫和框架的重要工具之一,是使用到裝飾器或者元類的大型框架中的一個(gè)非常重要組件。
如下示例一個(gè)描述符及引用描述符類的代碼:
class Descriptors: def __init__(self, key, value_type): self.key = key self.value_type = value_type def __get__(self, instance, owner): print("===> 執(zhí)行Descriptors的 get") return instance.__dict__[self.key] def __set__(self, instance, value): print("===> 執(zhí)行Descriptors的 set") if not isinstance(value, self.value_type): raise TypeError("參數(shù)%s必須為%s" % (self.key, self.value_type)) instance.__dict__[self.key] = value def __delete__(self, instance): print("===> 執(zhí)行Descriptors的delete") instance.__dict__.pop(self.key) class Person: name = Descriptors("name", str) age = Descriptors("age", int) def __init__(self, name, age): self.name = name self.age = age person = Person("xu", 15) print(person.__dict__) person.name person.name = "xu-1" print(person.__dict__) # 結(jié)果: # ===> 執(zhí)行Descriptors的 set # ===> 執(zhí)行Descriptors的 set # {'name': 'xu', 'age': 15} # ===> 執(zhí)行Descriptors的 get # ===> 執(zhí)行Descriptors的 set # {'name': 'xu-1', 'age': 15}
其中,Descriptors類就是一個(gè)描述符,Person是使用描述符的類。
類的__dict__屬性是類的一個(gè)內(nèi)置屬性,類的靜態(tài)函數(shù)、類函數(shù)、普通函數(shù)、全局變量以及一些內(nèi)置的屬性都是放在類__dict__里。
在輸出描述符的變量時(shí),會(huì)調(diào)用描述符中的__get__方法,在設(shè)置描述符變量時(shí),會(huì)調(diào)用描述符中的__set__方法。
二、描述符的種類和優(yōu)先級(jí)
描述符分為數(shù)據(jù)描述符和非數(shù)據(jù)描述符。
至少實(shí)現(xiàn)了內(nèi)置__set__()和__get__()方法的描述符稱為數(shù)據(jù)描述符;實(shí)現(xiàn)了除__set__()以外的方法的描述符稱為非數(shù)據(jù)描述符。
描述符的優(yōu)先級(jí)的高低順序:類屬性 > 數(shù)據(jù)描述符 > 實(shí)例屬性 > 非數(shù)據(jù)描述符 > 找不到的屬性觸發(fā)__getattr__()。
在上述“描述符定義”章節(jié)的例子中,實(shí)例person的屬性優(yōu)先級(jí)低于數(shù)據(jù)描述符Descriptors,所以在賦值或獲取值過程中,均調(diào)用了描述符的方法。
class Descriptors: def __get__(self, instance, owner): print("===> 執(zhí)行 Descriptors get") def __set__(self, instance, value): print("===> 執(zhí)行 Descriptors set") def __delete__(self, instance): print("===> 執(zhí)行 Descriptors delete") class University: name = Descriptors() def __init__(self, name): self.name = name
類屬性 > 數(shù)據(jù)描述符
# 類屬性 > 數(shù)據(jù)描述符 # 在調(diào)用類屬性時(shí),原來字典中的數(shù)據(jù)描述法被覆蓋為 XX-XX print(University.__dict__) # {..., 'name': <__main__.Descriptors object at 0x7ff8c0eda278>,} University.name = "XX-XX" print(University.__dict__) # {..., 'name': 'XX-XX',}
數(shù)據(jù)描述符 > 實(shí)例屬性
# 數(shù)據(jù)描述符 > 實(shí)例屬性 # 調(diào)用時(shí)會(huì)現(xiàn)在實(shí)例里面找,找不到name屬性,到類里面找,在類的字典里面找到 'name': <__main__.Descriptors object at 0x7fce16180a58> # 初始化時(shí)調(diào)用 Descriptors 的 __set__; un.name 調(diào)用 __get__ print(University.__dict__) un = University("xx-xx") un.name # 結(jié)果: # {..., 'name': <__main__.Descriptors object at 0x7ff8c0eda278>,} # ===> 執(zhí)行 Descriptors set # ===> 執(zhí)行 Descriptors get
下面是測試 實(shí)例屬性、 非數(shù)據(jù)描述符、__getattr__ 使用代碼
class Descriptors: def __get__(self, instance, owner): print("===>2 執(zhí)行 Descriptors get") class University: name = Descriptors() def __init__(self, name): self.name = name def __getattr__(self, item): print("---> 查找 item = {}".format(item))
實(shí)例屬性 > 非數(shù)據(jù)描述符
# 實(shí)例屬性 > 非數(shù)據(jù)描述符 # 在創(chuàng)建實(shí)例的時(shí)候,會(huì)在屬性字典中添加 name,后面調(diào)用 un2.name 訪問,都是訪問實(shí)例字典中的 name un2 = University("xu2") print(un2.name) # xu 并沒有調(diào)用到 Descriptors 的 __get__ print(un2.__dict__) # {'name': 'xu2'} un2.name = "xu-2" print(un2.__dict__) # {'name': 'xu-2'}
非數(shù)據(jù)描述符 > 找不到的屬性觸發(fā)__getattr__
# 非數(shù)據(jù)描述符 > 找不到的屬性觸發(fā)__getattr__ # 找不到 name1 使用 __getattr__ un3 = University("xu3") print(un3.name1) # ---> 查找 item = name1
三、描述符的應(yīng)用
使用描述符檢驗(yàn)數(shù)據(jù)類型
class Typed: def __init__(self, key, type): self.key = key self.type = type def __get__(self, instance, owner): print("---> get 方法") # print("instance = {}, owner = {}".format(instance, owner)) return instance.__dict__[self.key] def __set__(self, instance, value): print("---> set 方法") # print("instance = {}, value = {}".format(instance, value)) if not isinstance(value, self.type): # print("設(shè)置name的值不是字符串: type = {}".format(type(value))) # return raise TypeError("設(shè)置{}的值不是{},當(dāng)前傳入數(shù)據(jù)的類型是{}".format(self.key, self.type, type(value))) instance.__dict__[self.key] = value def __delete__(self, instance): print("---> delete 方法") # print("instance = {}".format(instance)) instance.__dict__.pop(self.key) class Person: name = Typed("name", str) age = Typed("age", int) def __init__(self, name, age, salary): self.name = name self.age = age self.salary = salary p1 = Person("xu", 99, 100.0) print(p1.__dict__) p1.name = "XU1" print(p1.__dict__) del p1.name print(p1.__dict__) # 結(jié)果: # ---> set 方法 # ---> set 方法 # {'name': 'xu', 'age': 99, 'salary': 100.0} # ---> set 方法 # {'name': 'XU1', 'age': 99, 'salary': 100.0} # ---> delete 方法 # {'age': 99, 'salary': 100.0}
四、描述符 + 類裝飾器 (給 Person類添加類屬性)
類裝飾器,道理和函數(shù)裝飾器一樣
def Typed(**kwargs): def deco(obj): for k, v in kwargs.items(): setattr(obj, k, v) return obj return deco @Typed(x=1, y=2) # 1、Typed(x=1, y=2) ==> deco 2、@deco ==> Foo = deco(Foo) class Foo: pass # 通過類裝飾器給類添加屬性 print(Foo.__dict__) # {......, 'x': 1, 'y': 2} print(Foo.x)
使用描述符和類裝飾器給 Person類添加類屬性
""" 描述符 + 類裝飾器 """ class Typed: def __init__(self, key, type): self.key = key self.type = type def __get__(self, instance, owner): print("---> get 方法") # print("instance = {}, owner = {}".format(instance, owner)) return instance.__dict__[self.key] def __set__(self, instance, value): print("---> set 方法") # print("instance = {}, value = {}".format(instance, value)) if not isinstance(value, self.type): # print("設(shè)置name的值不是字符串: type = {}".format(type(value))) # return raise TypeError("設(shè)置{}的值不是{},當(dāng)前傳入數(shù)據(jù)的類型是{}".format(self.key, self.type, type(value))) instance.__dict__[self.key] = value def __delete__(self, instance): print("---> delete 方法") # print("instance = {}".format(instance)) instance.__dict__.pop(self.key) def deco(**kwargs): def wrapper(obj): for k, v in kwargs.items(): setattr(obj, k, Typed(k, v)) return obj return wrapper @deco(name=str, age=int) class Person: # name = Typed("name", str) # age = Typed("age", int) def __init__(self, name, age, salary): self.name = name self.age = age self.salary = salary p1 = Person("xu", 99, 100.0) print(Person.__dict__) print(p1.__dict__) p1.name = "XU1" print(p1.__dict__) del p1.name print(p1.__dict__) # 結(jié)果: # ---> set 方法 # ---> set 方法 # {..., 'name': <__main__.Typed object at 0x7f3d79729dd8>, 'age': <__main__.Typed object at 0x7f3d79729e48>} # {'name': 'xu', 'age': 99, 'salary': 100.0} # ---> set 方法 # {'name': 'XU1', 'age': 99, 'salary': 100.0} # ---> delete 方法 # {'age': 99, 'salary': 100.0}
五、利用描述符自定義 @property
class Lazyproperty: def __init__(self, func): self.func = func def __get__(self, instance, owner): print("===> Lazypropertt.__get__ 參數(shù): instance = {}, owner = {}".format(instance, owner)) if instance is None: return self res = self.func(instance) setattr(instance, self.func.__name__, res) return self.func(instance) # def __set__(self, instance, value): # pass class Room: def __init__(self, name, width, height): self.name = name self.width = width self.height = height # @property # area=property(area) @Lazyproperty # # area=Lazyproperty(area) def area(self): return self.width * self.height # @property 測試代碼 # r = Room("房間", 2, 3) # print(Room.__dict__) # {..., 'area': <property object at 0x7f8b42de5ea8>,} # print(r.area) # 6 # r2 = Room("房間2", 2, 3) # print(r2.__dict__) # {'name': '房間2', 'width': 2, 'height': 3} # print(r2.area) # print(Room.area) # <__main__.Lazyproperty object at 0x7faabd589a58> r3 = Room("房間3", 2, 3) print(r3.area) print(r3.area) # 結(jié)果(只計(jì)算一次): # ===> Lazypropertt.__get__ 參數(shù): instance = <__main__.Room object at 0x7fd98e3757b8>, owner = <class '__main__.Room'> # 6 # 6
六、property 補(bǔ)充
class Foo: @property def A(self): print("===> get A") @A.setter def A(self, val): print("===> set A, val = {}".format(val)) @A.deleter def A(self): print("===> del A") f = Foo() f.A # ===> get A f.A = "a" # ===> set A, val = a del f.A # ===> del A class Foo: def get_A(self): print("===> get_A") def set_A(self, val): print("===> set_A, val = {}".format(val)) def del_A(self): print("===> del_A") A = property(get_A, set_A, del_A) f = Foo() f.A # ===> get_A f.A = "a" # ===> set_A, val = a del f.A # ===> del_A
到此這篇關(guān)于Python基礎(chǔ)詳解之描述符的文章就介紹到這了,更多相關(guān)Python描述符內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
python實(shí)現(xiàn)多進(jìn)程并發(fā)控制Semaphore與互斥鎖LOCK
本文主要介紹了python實(shí)現(xiàn)多進(jìn)程并發(fā)控制Semaphore與互斥鎖LOCK,通過實(shí)例來介紹互斥鎖和進(jìn)程并發(fā)控制 semaphore的具體使用,感興趣的同學(xué)可以了解一下2021-05-05Python中文分詞工具之結(jié)巴分詞用法實(shí)例總結(jié)【經(jīng)典案例】
這篇文章主要介紹了Python中文分詞工具之結(jié)巴分詞用法,結(jié)合實(shí)例形式總結(jié)分析了Python針對(duì)中文文件的讀取與分詞操作過程中遇到的問題與解決方法,需要的朋友可以參考下2017-04-04python使用pywinauto驅(qū)動(dòng)微信客戶端實(shí)現(xiàn)公眾號(hào)爬蟲
這個(gè)項(xiàng)目是通過pywinauto控制windows(win10)上的微信PC客戶端來實(shí)現(xiàn)公眾號(hào)文章的抓取。代碼分成server和client兩部分。server接收client抓取的微信公眾號(hào)文章,并且保存到數(shù)據(jù)庫。另外server支持簡單的搜索和導(dǎo)出功能。client通過pywinauto實(shí)現(xiàn)微信公眾號(hào)文章的抓取。2021-05-05如何使用python-opencv批量生成帶噪點(diǎn)噪線的數(shù)字驗(yàn)證碼
這篇文章主要介紹了如何使用python-opencv批量生成帶噪點(diǎn)噪線的數(shù)字驗(yàn)證碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12Python3使用PyQt5制作簡單的畫板/手寫板實(shí)例
下面小編就為大家?guī)硪黄狿ython3使用PyQt5制作簡單的畫板/手寫板實(shí)例。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-10-10MAC平臺(tái)基于Python Appium環(huán)境搭建過程圖解
這篇文章主要介紹了MAC平臺(tái)基于Python Appium環(huán)境搭建過程圖解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08PyCharm?設(shè)置數(shù)據(jù)庫,查詢數(shù)據(jù)庫語句方式
這篇文章主要介紹了PyCharm?設(shè)置數(shù)據(jù)庫,查詢數(shù)據(jù)庫語句方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07