1. 程式人生 > >Python——類的高級主題

Python——類的高級主題

主體 跟著 div *args obj 可調用對象 dsm str 又一

介紹關於類的一些高級主題,這些是可選的,在Python應用程序中,不會常常遇到。

==========================================================================

slots實例
將字符串屬性名稱順序賦值給特殊的__slots__類屬性。就能夠限制類的實例將有的合法屬性集。
這個特殊屬性通常是在class語句頂層內將字符串名稱順序賦值給變量__slots__而設置:僅僅有__slots__列表內的這些變量名可賦值為實例屬性。而實例屬性名必須在引用前賦值,即使是列在__slots__中也是這樣。看下述樣例:

>>> class limiter(object):
	__slots__ = ['age','name','job']

	
>>> x = limiter()
>>> x.age
Traceback (most recent call last):
  File "<pyshell#4>", line 1, in <module>
    x.age
AttributeError: age
>>> x.age = 40
>>> x.age
40
>>> x.ape = 1000
Traceback (most recent call last):
  File "<pyshell#7>", line 1, in <module>
    x.ape = 1000
AttributeError: 'limiter' object has no attribute 'ape'
假設創建了非常多實例而且僅僅有幾個屬性是必需的話。那麽為每一個實例對象分配一個命名空間字典可能在內存方面的代價過於昂貴。要節省空間和運行速度,slot屬性能夠順序存儲以供高速查找,而不是為每一個實例分配一個字典。


-------------------------------------------------------------------------------------------------------------------------------------

Slot和通用代碼
實際上。有些帶有slots的實例或許根本就沒有__dict__屬性字典。所以在有些程序中要使用比__dict__更為存儲中立的工具,比如getattr、setattr和dir內置函數。

>>> class C:
	__slots__ = ['a','b']

	
>>> X = C()
>>> X.a = 1
>>> X.a
1
>>> X.__dict__
Traceback (most recent call last):
  File "<pyshell#6>", line 1, in <module>
    X.__dict__
AttributeError: 'C' object has no attribute '__dict__'
>>> getattr(X,'a')
1
>>> setattr(X,'b',2)
>>> X.b
2
>>> 'a' in dir(X)
True
>>> 'b' in dir(X)
True
沒有屬性命名空間字典,不可能給不是slots列表中名稱的實例來分配新的名稱,然而,通過在__slots__中包括__dict__仍然能夠容納額外的屬性,從而考慮到一個屬性空間字典的須要。在這個樣例中。兩種機制都用到了,可是,getattr這種通用工具同意我們將它們當做單獨一組屬性對待:
>>> class D:
	__slots__ = ['a','b','__dict__']
	c = 3
	def __init__(self):
		self.d = 4

		
>>> X = D()
>>> X.d
4
>>> X.__dict__
{'d': 4}
>>> X.__slots__
['a', 'b', '__dict__']
>>> X.c
3
>>> X.a
Traceback (most recent call last):
  File "<pyshell#24>", line 1, in <module>
    X.a
AttributeError: a
>>> X.a = 1
>>> getattr(X,'a'),getattr(X,'c'),getattr(X,'d')
(1, 3, 4)
==========================================================================
類特性
有一種稱為特性(property)的機制,提供還有一種方式讓類定義自己主動調用的方法,來讀取或賦值實例屬性。
【特性和slots都是基於屬性描寫敘述器的新概念】

簡而言之,特性是一種對象,賦值給類屬性名稱。特性的產生是以三種方法(獲得、設置以及刪除運算的處理器)以及通過文檔字符串調用內置函數property。假設不論什麽參數以None傳遞或者省略。該運算就不能支持。


特性一般都是在class語句頂層賦值【比如,name = property(...)】。這樣賦值時。對類屬性本身的讀取(比如。obj.name),就會自己主動傳給property的一個讀取方法。例如以下例:

>>> class new():
	def getage(self):
		return 40
	age = property(getage,None,None,None)  # get,set,del,docs

	
>>> x = new()
>>> x.age
40
>>> x.name
Traceback (most recent call last):
  File "<pyshell#34>", line 1, in <module>
    x.name
AttributeError: 'new' object has no attribute 'name'
特性比傳統技術執行起來更快。

比如,當我們新增屬性賦值運算支持時,特性就變得更有吸引力:輸入的代碼更少,對我們不希望動態計算的屬性進行賦值運算時,不會發生額外的方法調用。

>>> class new():
	def getage(self):
		return 40
	def setage(self,value):
		print('set age:',value)
		self._age = value
	age = property(getage,setage,None,None)

	
>>> x = new()
>>> x.age
40
>>> x.age = 42
set age: 42
>>> x.age
40
>>> x._age
42
>>> x.job = 'trainer'
>>> x.job
'trainer'
==========================================================================

__getattribute__和描寫敘述符

__getattribute__能夠讓類攔截全部屬性的引用。而不局限於沒有定義的引用(假設__getattr__)。


除了特性和運算符重載方法,Python支持屬性描寫敘述符的概念——帶有__get__和__set__方法的類,分配給類屬性而且由實例繼承,這攔截了對特定屬性的讀取和寫入訪問。描寫敘述符在某種意義上是特性的一種更加通用的形式。

關於特性、__getattribute__和描寫敘述符這些高級話題將在興許逐步介紹。
==========================================================================

靜態方法

類方法通常在其第一個參數中傳遞一個實例對象。以充當方法調用的一個隱式主體。有時候。程序須要處理與類而不是與實例相關的數據,也就是我們須要一個類中的方法不僅不傳遞並且也不期待一個self實例參數。

Python通過【靜態方法】的概念來支持這種目標——嵌套在一個類中的沒有self參數的簡單函數。

比如。如果我們想使用類屬性去計算從一個類產生了多少實例。我們能夠把一個計數器存儲為類屬性。每次創建一個新的實例的時候。構造函數都會對計數器加1。而且,有一個顯示計數器值的方法。【記住,類屬性是由全部實例共享的】:

>>> class Spam:
	numInstances = 0
	def __init__(self):
		Spam.numInstances += 1
	def printNumInstances():
		print('Number of instances created:',Spam.numInstances)
printNumInstances方法旨在處理類數據而不是實例數據——它是關於全部實例的,而不是某個特定的實例。因此,我們想要不必傳遞一個實例就能夠調用它。

實際上,我們不想生成一個實例來獲取實例的數目。由於這本身就會改變我們想要獲取的實例的數目。

換句話說:我們想要一個無self的靜態方法。



我們能夠看一下測試結果:

>>> a = Spam()
>>> b = Spam()
>>> c = Spam()
>>> Spam.printNumInstances()
Number of instances created: 3
>>> a.printNumInstances()
Traceback (most recent call last):
  File "<pyshell#66>", line 1, in <module>
    a.printNumInstances()
TypeError: printNumInstances() takes 0 positional arguments but 1 was given
能夠看到,我們通過類調用無self方法執行成功。而通過實例調用無self方法出錯了,由於參數不正確。通過一個實例調用方法。這個實例會自己主動傳遞給方法的第一個參數中,可是這個無self方法並不存在參數來接收它。



假設你可以堅持僅僅通過類調用無self方法。那麽事實上你已經有了一個靜態方法了。然而假設你還想通過實例調用,那麽就須要採取其它設計,或者把這個方案標記為特殊的。
-------------------------------------------------------------------------------------------------------------------------------------

使用靜態方法
要將這個無self方法標記為靜態方法。須要調用內置函數staticmethod。例如以下:

>>> class Spam:
	numInstances = 0
	def __init__(self):
		Spam.numInstances += 1
	def printNumInstances():
		print('Number of instances created:',Spam.numInstances)
	printNumInstances = staticmethod(printNumInstances)  # staticmethod

	
>>> a = Spam()
>>> b = Spam()
>>> c = Spam()
>>> a.printNumInstances()
Number of instances created: 3
靜態方法對於處理一個類本地的數據來說是更好的解決方式。
==========================================================================

裝飾器和元類:初識

上邊提到的staticmethod可能比較奇怪。所以新增了一個功能,要讓這個運算變得簡單。【函數裝飾器】提供了一種方式,替函數明白了特定的運算模式。也就是將函數包裹了還有一層,在還有一函數的邏輯內實現。



函數裝飾器變成了通用的工具:除了靜態方法使用方法外。也可用於新增多種邏輯的函數。比如,能夠用來記錄函數調用的信息和在出錯時檢查傳入的參數類型等

Python提供了一下內置的函數裝飾器,來做一些運算,比如。標識靜態方法,可是我們也能夠編寫自己的隨意裝飾器。盡管不限於使用類,但用戶定義的函數裝飾器通常也寫成類,把原始函數和其它數據當成狀態信息。

-------------------------------------------------------------------------------------------------------------------------------------

函數裝飾器基礎
從語法上講,函數裝飾器是它後邊的函數的執行時的聲明。

函數裝飾器是寫成一行,就在定義函數或方法的def語句之前,[email protected]:也就是管理還有一函數的函數。

比如,如今的靜態方法我們能夠用下述的裝飾器語法編寫:

class C:
	@staticmethod
	def printNumInstances():
		...
這個語法和以下的寫法有同樣的效果:
class C:
	def meth():
		...
	meth = staticmethod(meth)
結果就是,調用方法函數的名稱。實際上是觸發了它staticmethod裝飾器的結果。


-------------------------------------------------------------------------------------------------------------------------------------

裝飾器樣例
之前介紹過,__call__運算符重載方法為類實例實現函數調用接口。

以下的程序通過這樣的方法定義類。在實例中存儲裝飾的函數,並捕捉對最初變量名的調用。

class tracer:
    def __init__(self,func):
        self.calls = 0
        self.func = func
    def __call__(self,*args):
        self.calls += 1
        print('call %s to %s'%(self.calls,self.func.__name__))
        self.func(*args)

@tracer
def spam(a,b,c):
    print(a,b,c)

spam(1,2,3)
spam('a','b','c')
spam(4,5,6)
由於spam函數時通過tracer裝飾器運行的,所以當最初的變量名spam調用時。實際上觸發的是類中的__call__方法,這種方法會計算和記錄該次調用,然後托付給原始的包裹的函數。

因此,此裝飾器可用於包裹攜帶隨意數目參數的不論什麽函數。



結果就是新增一層邏輯至原始的spam函數。執行結果例如以下:第一行來自tracer類,第二行來自spam函數。

call 1 to spam
1 2 3
call 2 to spam
a b c
call 3 to spam
4 5 6
這裏僅僅是初步了解。興許我們將會介紹各種各種的方法來編寫函數裝飾器。

-------------------------------------------------------------------------------------------------------------------------------------
類裝飾器和元類

類裝飾器類似於函數裝飾器,可是。它們在一條class語句的末尾執行,而且把一個類名又一次綁定到一個可調用對象,相同的,它們能夠用來管理類,或者當隨後創建實例的時候插入一個包裝邏輯層來管理實例。代碼結構例如以下:

def decorator(aClass):...

@decorator
class C:...
被映射為下列相當代碼:
def decorator(aClass):...

class C:...
C = decorator(C)
元類是一種類似的基於類的高級工具。其用途往往與類裝飾器有所重合。

它們提供了一種可選的模式。會把一個類對象的創建導向到頂級type類的一個子類。



關於裝飾器和元類的內容,將在之後更加具體地介紹。

Python——類的高級主題