類的授權
1.包裝
包裝在Python編程世界中時經常會被提到的一個術語。它是一個通用的名字,意思是對一個已存在的對象進行包裝,不管它是數據類型,還是一段代碼,可以是對一個已存在的對象,增加新的,刪除不要的,或者修改其他已存在的功能。
在Python2.2以前,從Python的標準類型子類化或派生類都是不允許的,即使你現在可以這么做,這種做法也并不多。你可以包裝任何類型作為一個類的核心成員,以使新對象的行為模仿你想要的數據類型中已存在的行為,并且去掉你不希望存在的行為;它可能會要做一些額外的事情。這就是“包裝類型”。在附錄中,我們還將討論如何擴充Python,包裝的另一種形式。
包裝包括定義一個類,它的實例擁有標準類型的核心行為,但它也通過新的或最新的功能,甚至可能通過訪問實際數據的不同方法得到提高。
還可以包裝類,但這不會有太大的用途,因為已經有擁有操作對象的機制,并且在之前已經描述過,我們采用派生對標準類型有對其進行包裝的方式。
2.實現授權
授權是包裝的一個特性,可用于簡化處理有關dictating功能,采用已存在的功能達到最大限度的代碼重用。
包裝一個類型通常是對已存在的類型的一些定制。我們在前面提到過,這種方法可以新建,修改或者刪除原有產品的功能。其它的則保持原樣,或者保留已存功能和行為。授權的過程,即是所有更新的功能都是有新類的某部分來處理,但已存在的功能就授權給對象的默認屬性。
實現授權的關鍵點就是覆蓋__getattr__()方法,在代碼中包含一個對getattr()內建函數的調用。特別地,調用getattr()以得到默認對象屬性(數據屬性或者方法)并返回它以便訪問或調用。特殊方法__getattr__()的工作方式是,當搜索一個屬性時,任何局部對象(定制的對象)首先被找到。如果搜索失敗了,則__getattr__()會被調用。然后調用getattr()得到一個對象的默認行為。
也就是說,引用一個屬性時,Python解釋器將試著在局部名稱空間中查找那個名字,比如一個自定義的方法或者局部實例屬性。如果沒有在局部字典中找到,則搜索類名稱空間,以防一個類屬性被訪問。最后,如果兩類搜索都失敗了,搜索對原對象開始授權請求,此時,__getattr__()會被調用。
包裝對象的簡例
這個類幾乎可以包裝任何對象,提供基本功能。比如使用rePR()和str()來處理字符串表示法, 定制由get()方法處理,它刪除包裝并且返回原始對象,所以保留的功能都授權給對象的本地屬性,在必要時,可又__getattr__()獲得。
>>> class WrapMe(object): def __init__(self, obj): self.__data = obj def get(self): return self.__data def __repr__(self): return `self.__data` def __str__(self): return str(self.__data) def __getattr__(self, attr): return getattr(self.__data, attr)
我們使用復數來舉第一個例子,因為復數有數據屬性及conjugate()內建方法。
>>> wrappedComplex = WrapMe(3.3+1.2j)>>> wrappedComplex(3.3+1.2j)>>> wrappedComplex.real3.3>>> wrappedComplex.imag1.2>>> wrappedComplex.conjugate()(3.3-1.2j)>>> wrappedComplex.get()(3.3+1.2j)
一旦我們創建了包裝的對象類型,只要由交互解釋器調用repr(),就可以得到一個字符串表示,然后我們訪問了復數的三種屬性,而這在類中一種都沒有定義。對這種屬性的訪問,是通過__getattr__()方法,授權給對象。最終調用的get()方法沒有授權,因為它是為我們對象單獨定義的。
然后是一個列表的例子
>>> wrappedList = WrapMe([123, 'foo', 45.67])>>> wrappedList.append('bar')>>> wrappedList.append(123)>>> wrappedList[123, 'foo', 45.67, 'bar', 123]>>> wrappedList.index(45.67)2>>> wrappedList.count(123)2>>> wrappedList.pop()123>>> wrappedList[123, 'foo', 45.67, 'bar']
注意,盡管我們正在我們的例子中使用實例,它們展示的行為與他們包裝的數據類型非常相似。然后需要明白,只有已存在的屬性是在此代碼中授權的。
但是也不是所有的行為都能被訪問,比如:
>>> wrappedList[3]Traceback (most recent call last):File "<pyshell#29>", line 1, in <module>wrappedList[3]TypeError: 'WrapMe' object does not support indexing
對于列表的索引切片操作,它是內建于類型中的,而不是像append()方法那樣作為屬性存在的。所以不能被訪問。從另一個角度來說,切片操作符是序列類型的一部分,并不是通過__getitem__()這樣的特殊方法來實現的。
我們有一種“作弊”的方法,訪問實際對象,然后用它的切片能力:
>>> realList = wrappedList.get()>>> realList[3]'bar'
這就是我們實現get()方法的原因了,我們可以從訪問調用中直接訪問對象的屬性,而忽略局部變量。
這里是簡單包裝類的例子。我們還剛開始接觸使用類型模擬來進行類自定義。你將會發現你可以進行無限多的改進,來進一步增加代碼的用途。
更新簡單的包裹類
創建時間,修改時間,及訪問時間是文件的幾個常見屬性。我們將給一個類添加時間屬性,創建時間('ctime')是實例化的時間,修改時間('mtime')是核心數據升級的時間,而訪問時間('atime')是最后一次對象數據值被獲取或者屬性被訪問的時間戳。
我們更新前面定義的類,創建一個模塊twrapme.py
代碼如下:
from time import time, ctimeclass TimeWrapMe(object): def __init__(self, obj): self.__data = obj self.__ctime = self.__mtime = / self.__atime = time() def get(self): self.__atime = time() return self.__data def gettimeval(self, t_type): if not isinstance(t_type, str) or / t_type[0] not in 'cma': raise TypeError, / "argument of 'c', 'm', or 'a' req'd" return getattr(self, '_%s__%stime' % / (self.__class__.__name__, t_type[0])) def gettimestr(self, t_type): return ctime(self.gettimeval(t_type)) def set(self, obj): self.__data = obj self.__mtime = self.atime = time() def __repr__(self): self.__atime = time() return `self.__data` def __str__(self): self.__atime = time() return str(self.__data) def __getattr__(self, attr): self.__atime = time() return getattr(self.__data, attr)
測試如下:
>>> TimeWrappedObj = TimeWrapMe(123)>>> TimeWrappedObj.gettimestr('c')'Tue Sep 29 17:25:11 2015'>>> TimeWrappedObj.gettimestr('m')'Tue Sep 29 17:25:11 2015'>>> TimeWrappedObj.gettimestr('a')'Tue Sep 29 17:25:11 2015'>>> TimeWrappedObj123>>> TimeWrappedObj.gettimestr('c')'Tue Sep 29 17:25:11 2015'>>> TimeWrappedObj.gettimestr('m')'Tue Sep 29 17:25:11 2015'>>> TimeWrappedObj.gettimestr('a')'Tue Sep 29 17:25:26 2015'>>> TimeWrappedObj.set('Update time!')>>> TimeWrappedObj.gettimestr('m')'Tue Sep 29 17:28:06 2015'>>> TimeWrappedObj'Update time!'>>> TimeWrappedObj.gettimestr('c')'Tue Sep 29 17:25:11 2015'>>> TimeWrappedObj.gettimestr('m')'Tue Sep 29 17:28:06 2015'>>> TimeWrappedObj.gettimestr('a')'Tue Sep 29 17:28:19 2015'
新聞熱點
疑難解答