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

歡迎訪問 生活随笔!

生活随笔

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

python

属性python_Python属性描述符(一)

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

描述符是對多個屬性運用相同存取邏輯的一種方式,,是實現了特性協議的類,這個協議包括了__get__、__set__和__delete__方法。property類實現了完整的描述符協議。通常,可以只實現部分協議,如只實現了__get__或__set__,而不必把__get__、__set__和__delete__全部實現

現在,讓我們用描述符協議升級上一個章節Python動態屬性和特性(二)的LineItem類

圖1-1

我們將定義一個Quantity類,LineItem類會用到兩個Quantity實例:一個用于管理 weight屬性,另一個用于管理 price屬性。weight這個屬性出現了兩次,但兩次都有不同,一個是LineItem的類屬性,另一個是各個LineItem 對象的實例屬性,同理price

現在,讓我們看一些定義:

描述符類:實現描述符協議的類,比如__set__、__get__或__delete__方法,如圖1-1的Quantity類

托管類:把描述符實例聲明為類屬性的類,如圖1-1中的LineItem類中的weight和price都為類屬性,都為Quantity描述符類的實例

描述符實例:描述符類的各個實例, 聲明為托管類的類屬性,如LineItem類中的weight和price屬性

托管實例:托管類的實例,在圖1-1中,LineItem類的實例即為托管類實例

儲存屬性:托管實例中存儲自身托管屬性的屬性。在圖1-1中,LineItem實例的weight和price屬性是儲存屬性。這種屬性與描述符屬性不同,描述符屬性都是類屬性

托管屬性:托管類中由描述符實例處理的公開屬性,值存儲在儲存屬性中。也就是說,描述符實例和儲存屬性為托管屬性建立了基礎

下面,讓我們來看一個例子

class Quantity: # <3>

def __init__(self, storage_name): # <4>

self.storage_name = storage_name

def __set__(self, instance, value): # <5>

if value > 0:

instance.__dict__[self.storage_name] = value

else:

raise ValueError('value must be > 0')

class LineItem:

weight = Quantity('weight') # <1>

price = Quantity('price')

def __init__(self, description, weight, price): # <2>

self.description = description

self.weight = weight

self.price = price

def subtotal(self):

return self.weight * self.price

我們將上面的代碼與之前的定義對應起來,首先是Quantity類,我們之前說過,只要實現了__set__、__get__或__delete__方法的類,就是描述符類,所以Quantity毫無疑問的是描述符類,再來是LineItem,根據之前的定義,托管類中的類屬性,是描述符類的實例,LineItem類的weight和price兩個類屬性都是Quantity描述符類的實例,所以LineItem類即為托管類,再來,我們根據代碼中的標號分析一下代碼:

LineItem中兩個屬性weight和price為描述符實例

當實例化一個LineItem對象時,需傳入weight和price參數,由于這兩個屬性實現了描述符協議,所以關于weight和price的讀值、取值或者刪除值都可能關聯到對應同名類屬性Quantity實例中方法,由于Quantity類中只實現了__set__方法,所以這里讀值和刪除值不會觸發Quantity實例中的方法

Quantity為描述符類

Quantity實例有個storage_name屬性,這是托管實例中存儲值的屬性的名稱

當我們要設置LineItem實例中的weight或者price屬性,則會觸發__set__方法,這個方法中self為描述符實例,即為LineItem類中的weight或price的Quantity實例,instance為托管類實例,即為LineItem實例,value是我們要設置的值,如果判斷value大于0,則將其屬性名和屬性值設置到instance.__dict__字典里

現在讓我們來測試這個類,我們故意將傳入的price設為0:

truffle = LineItem('White truffle', 100, 0)

運行結果:

Traceback (most recent call last):

……

ValueError: value must be > 0

可以看到,在設置值得時候確實觸發了__set__方法

另外還要重復聲明一點:__set__方法中的參數,self和instance分別為描述符實例和托管類實例,instance代表要設置屬性的那個對象,而self(描述符實例)則保存了要設置屬性的屬性名,在上個例子中,如果我們在__set__方法要設置LineItem實例只能用這樣的方式:

instance.__dict__[self.storage_name] = value

如果嘗試用setattr()方法來賦值

class Quantity:

def __init__(self, storage_name):

self.storage_name = storage_name

def __set__(self, instance, value):

if value > 0:

setattr(instance, self.storage_name, value)

else:

raise ValueError('value must be > 0')

測試:

truffle = LineItem('White truffle', 100, 10)

運行結果:

Traceback (most recent call last):

……

RecursionError: maximum recursion depth exceeded

我們會發現,如果用setattr()方法來賦值,會產生堆棧異常,為什么會這樣呢?假設obj是LineItem實例,obj.price = 10和setattr(obj, "price", 10)一樣,都會調用__set__方法,如果用setattr()方法來設置值,會不斷調用__set__方法,最終產生堆棧異常

上面的例子,LineItem有個缺點,在托管類中每次實例化描述符時都要重復輸入屬性名,現在,讓我們再改造一下LineItem類,使得不需要輸入屬性名。為了避免在描述符實例中重復輸入屬性名,我們將每個Quantity實例中的storage_name屬性生成一個獨一無二的字符串,同時為描述符類加上__get__方法

import uuid

class Quantity:

def __init__(self): # <1>

cls = self.__class__

prefix = cls.__name__

identity = str(uuid.uuid4())[:8]

self.storage_name = '_{}#{}'.format(prefix, identity)

def __get__(self, instance, owner): # <2>

return getattr(instance, self.storage_name)

def __set__(self, instance, value): # <3>

if value > 0:

setattr(instance, self.storage_name, value)

else:

raise ValueError('value must be > 0')

class LineItem:

weight = Quantity()

price = Quantity()

def __init__(self, description, weight, price):

self.description = description

self.weight = weight

self.price = price

def subtotal(self):

return self.weight * self.price

測試:

raisins = LineItem('Golden raisins', 10, 6.95)

print(raisins.weight, raisins.description, raisins.price)

運行結果:

10 Golden raisins 6.95

這里的Quantity描述符類在實例化時,我們不再要求需要傳入一個storage_name了,而是在初始化方法中生成一個storage_name,這個storage_name由類名和uuid生成的隨機字符串組成

我們知道,如果我們對一個實例中的屬性賦值,如果這個屬性名在類中定義為描述符實例,在賦值時會自動觸發__set__方法,而__get__方法則是在我們讀值的時候自動觸發,__get__方法除了self(描述符實例)還會傳入兩個參數,instance和owner,instance是托管類實例,owner是托管類,在我們上面的例子instance即為LineItem的實例,owner即LineItem類,當讀取實例中的一個屬性,如果這個屬性在類中定義為描述符實例,則會觸發__get__方法

在__set__方法中,我們不再調用instance.__dict__[self.storage_name] = value的方式來賦值,而是直接使用setattr()方法來賦值。上一個例子中,我們測試了如果用setattr()方法來賦值的話會出現堆棧溢出的異常,那為什么我們這里又可以用了呢?是因為,我們真正存儲屬性值的時候,用的屬性名并不是類的描述符名,而是由Python解釋器生成一個Quantity_#_{uuid}隨機字符串,而這個隨機字符串,而這個字符串并未在類中注冊為描述符實例,所以我們調用setattr(),不會再像之前那樣產生堆棧異常

這里還有一點,當我們嘗試打印一下LineItem.weight這個描述符實例

LineItem.weight

運行結果:

Traceback (most recent call last):

……

return getattr(instance, self.storage_name)

AttributeError: 'NoneType' object has no attribute '_Quantity#f9860e73'

我們會發現,訪問LineItem.weight會拋出AttributeError異常,因為在訪問LineItem.weight屬性時,同樣會調用__get__方法,這個時候instance傳入的是一個None,為了解決這個問題,我們在__get__方法中檢測,如果傳入的instance為None,則返回當前描述符實例,如果instance不為None,則返回instance中的實例屬性

import uuid

class Quantity:

def __init__(self):

cls = self.__class__

prefix = cls.__name__

identity = str(uuid.uuid4())[:8]

self.storage_name = '_{}#{}'.format(prefix, identity)

def __get__(self, instance, owner):

if instance is None:

return self

else:

return getattr(instance, self.storage_name)

def __set__(self, instance, value):

if value > 0:

setattr(instance, self.storage_name, value)

else:

raise ValueError('value must be > 0')

這里我們修改另外一個章節Python動態屬性和特性(二)中的quantity()特性工廠方法,使之不需要傳入storage_name

import uuid

def quantity():

storage_name = '_{}:{}'.format('quantity', str(uuid.uuid4())[:8])

def qty_getter(instance):

return instance.__dict__[storage_name]

def qty_setter(instance, value):

if value > 0:

instance.__dict__[storage_name] = value

else:

raise ValueError('value must be > 0')

return property(qty_getter, qty_setter)

class LineItem:

weight = quantity()

price = quantity()

def __init__(self, description, weight, price):

self.description = description

self.weight = weight

self.price = price

def subtotal(self):

return self.weight * self.price

raisins = LineItem('Golden raisins', 10, 6.95)

print(raisins.weight, raisins.description, raisins.price)

運行結果:

10 Golden raisins 6.95

現在,我們對比一下描述符類和特性工廠,兩種方法都可以在對屬性設值或讀取時進行一些額外的操作,哪種更好呢?這里建議使用描述符類的方式,主要有兩個原因:

描述符類可以使用子類擴展,若想重用工廠函數中的代碼,除了復制黏貼,很難有其他的辦法

使用函數屬性和閉包保持狀態相比,在類屬性和實例屬性中保持狀態更易于理解

我們通過描述符類Quantity,在訪問和設置LineItem托管實例的weight和price時進行額外的操作,現在,讓我們更進一步,新增一個description描述符實例,對當要對LineItem實例的description屬性進行設置和訪問時,也增加一些操作。這里,我們要新增一個描述符類NotBlank,在設計NotBlank的過程中,我們發現它與Quantity描述符類很像,只是驗證邏輯不同

回想Quantity的功能,我們注意到它做了兩件不同的事,管理托管實例中的存儲屬性,以及驗證用于設置那兩個屬性的值。由此可見,我們可以通過繼承的方式,來復用描述符類,這里,我們創建兩個基類:

AutoStorage:自動管理儲存屬性的描述符類

Validated:擴展 AutoStorage 類的抽象子類,覆蓋 __set__ 方法,調用必須由子類實現的validate方法

稍后我們會重寫Quantity類,并實現NotBlank類,使它繼承Validated類,只編寫validate方法,類之間的關系如圖1-2:

圖1-2

圖1-2:幾個描述符類的層次結構。AutoStorage基類負責自動存儲屬性;Validated類做驗證,把職責委托給抽象方法validate;Quantity和NonBlank是Validated的具體子類。Validated、Quantity和NonBlank 三個類之間的關系體現了模板方法設計模式。

import abc

import uuid

class AutoStorage: # <1>

def __init__(self):

cls = self.__class__

prefix = cls.__name__

identity = str(uuid.uuid4())[:8]

self.storage_name = '_{}#{}'.format(prefix, identity)

def __get__(self, instance, owner):

if instance is None:

return self

else:

return getattr(instance, self.storage_name)

def __set__(self, instance, value):

setattr(instance, self.storage_name, value)

class Validated(abc.ABC, AutoStorage): # <2>

def __set__(self, instance, value): # <3>

value = self.validate(instance, value)

super().__set__(instance, value)

@abc.abstractmethod

def validate(self, instance, value): # <4>

"""return validated value or raise ValueError"""

class Quantity(Validated):

"""a number greater than zero"""

def validate(self, instance, value): # <5>

if value <= 0:

raise ValueError('value must be > 0')

return value

class NotBlank(Validated):

"""a string with at least one non-space character"""

def validate(self, instance, value): # <6>

value = value.strip()

if len(value) == 0:

raise ValueError('value cannot be empty or blank')

return value

AutoStorage類提供了之前Quantity描述符類的大部分功能

Validated類是抽象類,不過也同時繼承了AutoStorage類

Validated類中重寫__set__方法,先通過校驗方法,再調用父類的__set__方法來存儲值

抽象方法,具體實現由子類完成

Quantity實現了父類Validated的validate方法,校驗設置的值必須大于0

NotBlank實現了父類Validated的validate方法,校驗設置的值不能為空字符串

使用Quantity和NonBlank描述符的LineItem類

class LineItem:

description = NotBlank()

weight = Quantity()

price = Quantity()

def __init__(self, description, weight, price):

self.description = description

self.weight = weight

self.price = price

def subtotal(self):

return self.weight * self.price

測試新的LineItem類

raisins = LineItem(' ', 10, 6.95)

運行結果:

Traceback (most recent call last):

……

ValueError: value cannot be empty or blank

總結

以上是生活随笔為你收集整理的属性python_Python属性描述符(一)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。