糯米文學吧

位置:首頁 > 設計 > 網頁設計

Python中的類與對象之描述符解析

描述符(Descriptors)是Python語言中一個深奧但卻重要的一部分。它們廣泛應用於Python語言的內核,熟練掌握描述符將會為Python程序員的工具箱添加一個額外的技巧。以下是小編為大家搜索整理的Python中的類與對象之描述符解析,希望能給大家帶來幫助!更多精彩內容請及時關注我們應屆畢業生考試網!

Python中的類與對象之描述符解析

為了給接下來對描述符的討論做一些鋪墊,小編將描述一些程序員可能會在日常編程活動中遇到的場景,然後將解釋描述符是什麼,以及它們如何為這些場景提供優雅的解決方案。在這篇總結中,會使用新樣式類來指代Python版本。

  1、假設一個程序中,我們需要對一個對象屬性執行嚴格的類型檢查。然而,Python是一種動態語言,所以並不支持類型檢查,但是這並不妨礙我們實現自己版本,且較為初級的類型檢查。對象屬性類型檢查的傳統方法可能採用下面的方式:

?

123456789def __init__(self, name, age):if isinstance(str, name): = nameelse:raise TypeError("Must be a string")if isinstance(int, age): = ageelse:raise TypeError("Must be an int")

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

  2、假設在一個程序中,我們想創建一些在運行時立刻初始化然後變成只讀的屬性。有人也能想到利用Python中的`特殊方法來實現,但這種實現方法仍舊是笨拙和繁瑣的。

  3、最後,設想一個程序中,我們希望以某種方式自定義對象屬性的訪問。例如需要記錄這種屬性的訪問。同樣的,還是可以想到一個解決方法,即使這種解決方案可能比較笨重並且不可複用。

上述問題因都與屬性引用相關而全部聯繫在了一起。下面,我們將嘗試自定義屬性的訪問方法。

Python描述符

針對上面所列的問題,描述符提供了優雅、簡潔、健壯和可重用的解決方案。簡而言之,一個描述符就是一個對象,該對象代表了一個屬性的值。這就意味着如果一個賬户對象有一個屬性“name”,那麼描述符就是另一個能夠用來代表屬性“name”持有值的對象。描述符協議中“定義了__get__”、“__set__”或”__delete__” 這些特殊方法,描述符是實現其中一個或多個方法的對象。這些方法中每一種方法的簽名如下所示:

?

12345python (self,obj,type=None)->value。descr.__set__(self, obj, value) --> Nonedescr.__delete__(self, obj) --> None

實現__get__方法的對象是非數據描述符,意味着在初始化之後它們只能被讀取。而同時實現__get__和__set__的對象是數據描述符,意味着這種屬性是可寫的。

為了更好地理解描述符,我們給出針對上述問題基於描述符的解決方法。使用Python描述符實現對象屬性的類型檢查將是一個非常簡單的任務。裝飾器實現這種類型檢查的代碼如下所示:

?

1234567891011121314151617181920212223242526272829303132class TypedProperty(object):def __init__(self, name, type, default=None): = "_" + = ult = default if default else type()def __get__(self, instance, cls):return getattr(instance, , ult)def __set__(self,instance,value):if not isinstance(value,):raise TypeError("Must be a %s" % )setattr(instance,,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()>> = "obi">> = 1234>> print 1234>> print obi# trying to assign a string to number fails>> = '1234'TypeError: Must be a <type 'int'>

在這個例子中,我們實現了一個描述符TypedProperty,並且這個描述符類會對它所代表的類的任何屬性執行類型檢查。注意到這一點很重要,即描述符只能在類級別進行合法定義,而不能在實例級別定義。例如,在上面例子中的__init__方法裏。

當訪問類Foo實例的任何屬性時,描述符會調用它的__get__方法。需要注意的是,__get__方法的第一個參數是描述符代表的屬性被引用的源對象。當屬性被分配時,描述符會調用它的__set__方法。為了理解為什麼可以使用描述符代表對象屬性,我們需要理解Python中屬性引用解析的執行方式。對於對象來説,屬性解析機制在object.__getattribute__()中。該方法將b.x轉換成type(b).__dict__['x'].__get__(b, type(b))。然後,解析機制使用優先級鏈搜索屬性,在優先級鏈中,類字典中發現的數據描述符的優先級高於實例變量,實例變量優先級高於非數據描述符,如果提供了getattr(),優先級鏈會為getattr()分配最低優先級。對於一個給定的對象類,可以通過自定義__getattribute__方法來重寫優先級鏈。

深刻理解優先級鏈之後,就很容易想出針對前面提出的第二個和第三個問題的優雅解決方案了。那就是,利用描述符實現一個只讀屬性將變成實現數據描述符這個簡單的情況了,即不帶__set__方法的描述符。儘管在本例中不重要,定義訪問方式的問題只需要在__get__和__set__方法中增加所需的功能即可。

類屬性

每次我們想使用描述符的時候都不得不定義描述符類,這樣看起來非常繁瑣。Python特性提供了一種簡潔的方式用來向屬性增加數據描述符。一個屬性簽名如下所示:

?

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

fget、fset和fdel分別是類的getter、setter和deleter方法。我們通過下面的一個示例來説明如何創建屬性:

?

1234567891011121314class Accout(object):def __init__(self):self._acct_num = Nonedef get_acct_num(self):return self._acct_numdef set_acct_num(self, value):self._acct_num = valuedef del_acct_num(self):del self._acct_numacct_num = property(get_acct_num, set_acct_num, del_acct_num, "Account number property.")

如果acct是Account的一個實例,_num將會調用getter,_num = value將調用setter,del acct__num將調用deleter。

在Python中,屬性對象和功能可以像《描述符指南》中説明的那樣使用描述符協議來實現,如下所示:

?

123456789101112131415161718192021222324252627282930313233343536class Property(object):"Emulate PyProperty_Type() in Objects/descrobject.c"def __init__(self, fget=None, fset=None, fdel=None, doc=None): = = = fdelif doc is None and fget is not None:doc = fget.__doc__self.__doc__ = docdef __get__(self, obj, objtype=None):if obj is None:return selfif is None:raise AttributeError("unreadable attribute")return (obj)def __set__(self, obj, value):if is None:raise AttributeError("can't set attribute")(obj, value)def __delete__(self, obj):if is None:raise AttributeError("can't delete attribute")(obj)def getter(self, fget):return type(self)(fget, , , self.__doc__)def setter(self, fset):return type(self)(, fset, , self.__doc__)def deleter(self, fdel):return type(self)(, , fdel, self.__doc__)

Python也提供了@ property裝飾器,可以用它來創建只讀屬性。一個屬性對象擁有getter、setter和deleter裝飾器方法,可以使用它們通過對應的被裝飾函數的accessor函數創建屬性的拷貝。下面的例子最好地解釋了這一點:

?

1234567891011121314151617class C(object):def __init__(self):self._x = None@property# the x property. the decorator creates a read-only propertydef x(self):return self._er# the x property setter makes the property writeabledef x(self, value):self._x = terdef x(self):del self._x

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