Python中的類與對(duì)象之描述符詳解
來(lái)源:易賢網(wǎng) 閱讀:1176 次 日期:2015-03-31 15:42:04
溫馨提示:易賢網(wǎng)小編為您整理了“Python中的類與對(duì)象之描述符詳解”,方便廣大網(wǎng)友查閱!

這篇文章主要介紹了Python中的描述符詳解,屬于Python學(xué)習(xí)過(guò)程中類與對(duì)象的基本知識(shí),需要的朋友可以參考下

描述符(Descriptors)是Python語(yǔ)言中一個(gè)深?yuàn)W但卻重要的一部分。它們廣泛應(yīng)用于Python語(yǔ)言的內(nèi)核,熟練掌握描述符將會(huì)為Python程序員的工具箱添加一個(gè)額外的技巧。為了給接下來(lái)對(duì)描述符的討論做一些鋪墊,我將描述一些程序員可能會(huì)在日常編程活動(dòng)中遇到的場(chǎng)景,然后我將解釋描述符是什么,以及它們?nèi)绾螢檫@些場(chǎng)景提供優(yōu)雅的解決方案。在這篇總結(jié)中,我會(huì)使用新樣式類來(lái)指代Python版本。

1、假設(shè)一個(gè)程序中,我們需要對(duì)一個(gè)對(duì)象屬性執(zhí)行嚴(yán)格的類型檢查。然而,Python是一種動(dòng)態(tài)語(yǔ)言,所以并不支持類型檢查,但是這并不妨礙我們實(shí)現(xiàn)自己版本,且較為初級(jí)的類型檢查。對(duì)象屬性類型檢查的傳統(tǒng)方法可能采用下面的方式:

def __init__(self, name, age):

if isinstance(str, name):

self.name = name

else:

raise TypeError("Must be a string")

if isinstance(int, age):

self.age = age

else:

raise TypeError("Must be an int")

上面是執(zhí)行這種類型檢查的一種方法,但是參數(shù)數(shù)量增加時(shí)它將變得比較繁瑣。另外,在賦值之前,我們可以創(chuàng)建一個(gè)在__init__中調(diào)用的type_check(type, val)函數(shù),但是當(dāng)我們想在其他地方設(shè)置屬性值時(shí),該如何簡(jiǎn)單地實(shí)現(xiàn)這種檢查呢。我想到的一個(gè)快速解決方案是Java中的getters和setters,但是這并不符合Python風(fēng)格,并且比較麻煩。

2、假設(shè)在一個(gè)程序中,我們想創(chuàng)建一些在運(yùn)行時(shí)立刻初始化然后變成只讀的屬性。有人也能想到利用Python中的特殊方法來(lái)實(shí)現(xiàn),但這種實(shí)現(xiàn)方法仍舊是笨拙和繁瑣的。

3、最后,設(shè)想一個(gè)程序中,我們希望以某種方式自定義對(duì)象屬性的訪問(wèn)。例如需要記錄這種屬性的訪問(wèn)。同樣的,還是可以想到一個(gè)解決方法,即使這種解決方案可能比較笨重并且不可復(fù)用。

上述問(wèn)題因都與屬性引用相關(guān)而全部聯(lián)系在了一起。下面,我們將嘗試自定義屬性的訪問(wèn)方法。

Python描述符

針對(duì)上面所列的問(wèn)題,描述符提供了優(yōu)雅、簡(jiǎn)潔、健壯和可重用的解決方案。簡(jiǎn)而言之,一個(gè)描述符就是一個(gè)對(duì)象,該對(duì)象代表了一個(gè)屬性的值。這就意味著如果一個(gè)賬戶對(duì)象有一個(gè)屬性“name”,那么描述符就是另一個(gè)能夠用來(lái)代表屬性“name”持有值的對(duì)象。描述符協(xié)議中“定義了__get__”、“__set__”或”__delete__” 這些特殊方法,描述符是實(shí)現(xiàn)其中一個(gè)或多個(gè)方法的對(duì)象。這些方法中每一種方法的簽名如下所示:

python descr.get(self,obj,type=None)->value。

descr.__set__(self, obj, value) --> None

descr.__delete__(self, obj) --> None

實(shí)現(xiàn)__get__方法的對(duì)象是非數(shù)據(jù)描述符,意味著在初始化之后它們只能被讀取。而同時(shí)實(shí)現(xiàn)__get__和__set__的對(duì)象是數(shù)據(jù)描述符,意味著這種屬性是可寫(xiě)的。

為了更好地理解描述符,我們給出針對(duì)上述問(wèn)題基于描述符的解決方法。使用Python描述符實(shí)現(xiàn)對(duì)象屬性的類型檢查將是一個(gè)非常簡(jiǎn)單的任務(wù)。裝飾器實(shí)現(xiàn)這種類型檢查的代碼如下所示:

class TypedProperty(object):

def __init__(self, name, type, default=None):

self.name = "_" + name

self.type = type

self.default = default if default else type()

def __get__(self, instance, cls):

return getattr(instance, self.name, self.default)

def __set__(self,instance,value):

if not isinstance(value,self.type):

raise TypeError("Must be a %s" % self.type)

setattr(instance,self.name,value)

def __delete__(self,instance):

raise AttributeError("Can't delete attribute")

class Foo(object):

name = TypedProperty("name",str)

num = TypedProperty("num",int,42)

>> acct = Foo()

>> acct.name = "obi"

>> acct.num = 1234

>> print acct.num

1234

>> print acct.name

obi

# trying to assign a string to number fails

>> acct.num = '1234'

TypeError: Must be a <type 'int'>

在這個(gè)例子中,我們實(shí)現(xiàn)了一個(gè)描述符TypedProperty,并且這個(gè)描述符類會(huì)對(duì)它所代表的類的任何屬性執(zhí)行類型檢查。注意到這一點(diǎn)很重要,即描述符只能在類級(jí)別進(jìn)行合法定義,而不能在實(shí)例級(jí)別定義。例如,在上面例子中的__init__方法里。

當(dāng)訪問(wèn)類Foo實(shí)例的任何屬性時(shí),描述符會(huì)調(diào)用它的__get__方法。需要注意的是,__get__方法的第一個(gè)參數(shù)是描述符代表的屬性被引用的源對(duì)象。當(dāng)屬性被分配時(shí),描述符會(huì)調(diào)用它的__set__方法。為了理解為什么可以使用描述符代表對(duì)象屬性,我們需要理解Python中屬性引用解析的執(zhí)行方式。對(duì)于對(duì)象來(lái)說(shuō),屬性解析機(jī)制在object.__getattribute__()中。該方法將b.x轉(zhuǎn)換成type(b).__dict__['x'].__get__(b, type(b))。然后,解析機(jī)制使用優(yōu)先級(jí)鏈搜索屬性,在優(yōu)先級(jí)鏈中,類字典中發(fā)現(xiàn)的數(shù)據(jù)描述符的優(yōu)先級(jí)高于實(shí)例變量,實(shí)例變量?jī)?yōu)先級(jí)高于非數(shù)據(jù)描述符,如果提供了getattr(),優(yōu)先級(jí)鏈會(huì)為getattr()分配最低優(yōu)先級(jí)。對(duì)于一個(gè)給定的對(duì)象類,可以通過(guò)自定義__getattribute__方法來(lái)重寫(xiě)優(yōu)先級(jí)鏈。

深刻理解優(yōu)先級(jí)鏈之后,就很容易想出針對(duì)前面提出的第二個(gè)和第三個(gè)問(wèn)題的優(yōu)雅解決方案了。那就是,利用描述符實(shí)現(xiàn)一個(gè)只讀屬性將變成實(shí)現(xiàn)數(shù)據(jù)描述符這個(gè)簡(jiǎn)單的情況了,即不帶__set__方法的描述符。盡管在本例中不重要,定義訪問(wèn)方式的問(wèn)題只需要在__get__和__set__方法中增加所需的功能即可。

類屬性

每次我們想使用描述符的時(shí)候都不得不定義描述符類,這樣看起來(lái)非常繁瑣。Python特性提供了一種簡(jiǎn)潔的方式用來(lái)向?qū)傩栽黾訑?shù)據(jù)描述符。一個(gè)屬性簽名如下所示:

?

1

property(fget=None, fset=None, fdel=None, doc=None) -> property attribute

fget、fset和fdel分別是類的getter、setter和deleter方法。我們通過(guò)下面的一個(gè)示例來(lái)說(shuō)明如何創(chuàng)建屬性:

class Accout(object):

def __init__(self):

self._acct_num = None

def get_acct_num(self):

return self._acct_num

def set_acct_num(self, value):

self._acct_num = value

def del_acct_num(self):

del self._acct_num

acct_num = property(get_acct_num, set_acct_num, del_acct_num, "Account number property.")

如果acct是Account的一個(gè)實(shí)例,acct.acct_num將會(huì)調(diào)用getter,acct.acct_num = value將調(diào)用setter,del acct_num.acct_num將調(diào)用deleter。

在Python中,屬性對(duì)象和功能可以像《描述符指南》中說(shuō)明的那樣使用描述符協(xié)議來(lái)實(shí)現(xiàn),如下所示:

class Property(object):

"Emulate PyProperty_Type() in Objects/descrobject.c"

def __init__(self, fget=None, fset=None, fdel=None, doc=None):

self.fget = fget

self.fset = fset

self.fdel = fdel

if doc is None and fget is not None:

doc = fget.__doc__

self.__doc__ = doc

def __get__(self, obj, objtype=None):

if obj is None:

return self

if self.fget is None:

raise AttributeError("unreadable attribute")

return self.fget(obj)

def __set__(self, obj, value):

if self.fset is None:

raise AttributeError("can't set attribute")

self.fset(obj, value)

def __delete__(self, obj):

if self.fdel is None:

raise AttributeError("can't delete attribute")

self.fdel(obj)

def getter(self, fget):

return type(self)(fget, self.fset, self.fdel, self.__doc__)

def setter(self, fset):

return type(self)(self.fget, fset, self.fdel, self.__doc__)

def deleter(self, fdel):

return type(self)(self.fget, self.fset, fdel, self.__doc__)

Python也提供了@ property裝飾器,可以用它來(lái)創(chuàng)建只讀屬性。一個(gè)屬性對(duì)象擁有g(shù)etter、setter和deleter裝飾器方法,可以使用它們通過(guò)對(duì)應(yīng)的被裝飾函數(shù)的accessor函數(shù)創(chuàng)建屬性的拷貝。下面的例子最好地解釋了這一點(diǎn):

class C(object):

def __init__(self):

self._x = None

@property

# the x property. the decorator creates a read-only property

def x(self):

return self._x

@x.setter

# the x property setter makes the property writeable

def x(self, value):

self._x = value

@x.deleter

def x(self):

del self._x

如果我們想讓屬性只讀,那么我們可以去掉setter方法。

在Python語(yǔ)言中,描述符有著廣泛的應(yīng)用。Python函數(shù)、類方法、靜態(tài)方法都是非數(shù)據(jù)描述符的例子。針對(duì)列舉的Python對(duì)象是如何使用描述符實(shí)現(xiàn)的問(wèn)題,《描述符指南》給出了一個(gè)基本的描述。

更多信息請(qǐng)查看IT技術(shù)專欄

更多信息請(qǐng)查看數(shù)據(jù)庫(kù)
易賢網(wǎng)手機(jī)網(wǎng)站地址:Python中的類與對(duì)象之描述符詳解
由于各方面情況的不斷調(diào)整與變化,易賢網(wǎng)提供的所有考試信息和咨詢回復(fù)僅供參考,敬請(qǐng)考生以權(quán)威部門(mén)公布的正式信息和咨詢?yōu)闇?zhǔn)!

2025國(guó)考·省考課程試聽(tīng)報(bào)名

  • 報(bào)班類型
  • 姓名
  • 手機(jī)號(hào)
  • 驗(yàn)證碼
關(guān)于我們 | 聯(lián)系我們 | 人才招聘 | 網(wǎng)站聲明 | 網(wǎng)站幫助 | 非正式的簡(jiǎn)要咨詢 | 簡(jiǎn)要咨詢須知 | 加入群交流 | 手機(jī)站點(diǎn) | 投訴建議
工業(yè)和信息化部備案號(hào):滇ICP備2023014141號(hào)-1 云南省教育廳備案號(hào):云教ICP備0901021 滇公網(wǎng)安備53010202001879號(hào) 人力資源服務(wù)許可證:(云)人服證字(2023)第0102001523號(hào)
云南網(wǎng)警備案專用圖標(biāo)
聯(lián)系電話:0871-65099533/13759567129 獲取招聘考試信息及咨詢關(guān)注公眾號(hào):hfpxwx
咨詢QQ:526150442(9:00—18:00)版權(quán)所有:易賢網(wǎng)
云南網(wǎng)警報(bào)警專用圖標(biāo)