Python中編寫類的各類技巧和方法

簡介python

有關 Python 內編寫類的各類技巧和方法(構建和初始化、重載操做符、類描述、屬性訪問控制、自定義序列、反射機制、可調用對象、上下文管理、構建描述符對象、Pickling)。 你能夠把它看成一個教程,進階,或者使用參考;我但願它可以成爲一份針對 Python 方法的用戶友好指南。程序員

內容目錄數據庫

介紹
構建和初始化
使操做符在自定義類內工做
神奇方法——比較
神奇方法——數字
描述你的類
屬性訪問控制
製做自定義序列
反射
可調用對象
上下文管理
構建描述符對象
Pickling 你的對象

總結
附錄:如何調用神奇方法

1.介紹編程

這份指南是幾個月內最有價值的 Blog 投稿精華。它的主題是向你們講述 Python 中的神奇方法。數組

何爲神奇方法呢?它們是面向 Python 中的一切,是一些特殊的方法容許在本身的定義類中定義增長「神奇」的功能。它們老是使用雙下劃線(好比 __ init__ 或 __ lt__),但它們的文檔沒有很好地把它們表現出來。全部這些神奇方法都出如今Python的官方文檔中,但內容相對分散,組織結構也顯得鬆散。還有你會難以發現一個實例(雖然他們被設計很棒,在語言參考中被詳細描述,可以後就會伴隨着枯燥的語法描述等)。緩存

因此,爲了解決我認爲在 Python 文檔中的一大敗筆,我打算用更多純英語,實例驅動的文檔來講明 Python 的神奇方法。而後我就開始花了幾周的時間來寫 blog,而如今我已經完成了它們,並將它們合訂成一份指南。安全

我但願你喜歡它。把它看成一個教程,進階,或者使用參考;我但願它可以成爲一份針對 Python 方法的用戶友好指南。數據結構

2.構建和初始化app

相信你們都熟悉這個最基礎的神奇方法 __ init__。它令你能自定義一個對象的初始化行爲。而當我調用x=SomeClass() 時, init 並非最早被調用的。實際上有一個叫作 new 的方法,事實上是它建立了實例,它傳遞任何參數給初始化程序來達到建立的目的。在對象生命週期結束時,調用 del。讓咱們更近地觀察下這 3 個神奇方法吧:socket

  • __ new__(cls,[...)

一個對象的實例化時 __ new__ 是第一個被調用的方法。在類中傳遞其餘任何參數到 __ init__。__new__不多被使用,這樣作確實有其目的,特別是當一個子類繼承一個不可改變的類型(一個元組或一個字符串)時。我不打算再繼續深刻追求 new 的細節了,由於這不會產生多大用處,由於在 Python Docs 內已經涵蓋了一份巨詳細的說明了。

  • __ init__(self,[...)

類的初始化。它會得到初始構建調用傳過來的任何東西(舉例來講就是,當咱們調用x=SomeClass(10,'foo'),__ init__ 就會把傳過來的 10 和 'foo' 做爲參數。__init__在 Python 的類定義中幾乎廣泛被使用)

  • __ del__(self)

若是 __ new__和 init 是對象的構造器,那麼 del 就是析構器。它不實現聲明爲del x(這樣的代碼不會解釋成 x. del())的行爲。相反,它定義爲當一個對象被垃圾回收時的行爲。這可能對可能須要額外清理的對象至關有用,好比 sockets 或文件對象。但要當心,若是對象仍處於存活狀態而當被解釋退出時, del 沒有保證就會被執行,所以這樣的 del 不能做爲良好的編碼規範的替代。(就像當你完成操做老是要關閉一次鏈接。但事實上, del 幾乎永遠不會執行,就由於它處於不安全狀況被調用了。使用時保持警戒!)

把上述這些內容合在一塊兒,就成了一份 __ init__ 和 __ del__ 的實際使用用例:

from os.path import join
class FileObject:
'''對文件對象的包裝,確保文件在關閉時獲得刪除'''

def __init__(self, filepath='~', filename='sample.txt'):
# 按filepath,讀寫模式打開名爲filename的文件
self.file=open(join(filepath,filename), 'r+')

def __del__(self):
self.file.close()
del self.file

3.使操做符在自定義類內工做

使用 Python 神奇方法的優點之一就是它提供了一種簡單的方式能讓對象的行爲像內建類型。這意味着你能夠避免用醜陋,反直覺和非標準方法執行基本運算。在某些語言中,一般會這樣作:

if instance.equals(other_instance):
# do something

你也應該在 Python 確實會這樣作,但同時它會增長用戶的疑惑以及沒必要要的冗長。不一樣的庫可能會對相同的運算採用不一樣的命名,這使得用戶比日常幹了更多的事。依靠神奇方法的力量,你能夠定義一個方法(好比 __ eq__),而後帶代替咱們真實的意圖:

if instance == other_instance:
# do something

如今你看到的是神奇方法力量的一部分。絕大多數都容許咱們定義爲運算符自己的意義,當用在咱們本身定義的類上就像它們是內建類型。

3.1 神奇方法——比較

Python 有一整套神奇方法被設計用來經過操做符實現對象間直觀的比較,而非彆扭的方法調用。它們一樣提供了一套覆蓋 Python 對象比較的默認行爲(經過引用)。如下是這些方法的列表以及作法:

__ cmp__(self, other)

__ cmp__是神奇方法中最基礎的一個。實際上它實現全部比較操做符行爲(<,==,!=,等),但它有可能不按你想要的方法工做(例如,一個實例是否等於另外一個這取決於比較的準則,以及一個實例是否大於其餘的這也取決於其餘的準則)。若是 self < other,那 cmp 應當返回一個負整數;若是 self == other,則返回 0;若是 self > other,則返回正整數。它一般是最好的定義,而不須要你一次就全定義好它們,但當你須要用相似的準則進行全部的比較時, cmp 會是一個很好的方式,幫你節省重複性和提升明確度。

__ eq__(self, other)

定義了相等操做符,==的行爲。

__ ne__(self, other)

定義了不相等操做符,!= 的行爲。

__ lt__(self, other)

定義了小於操做符,< 的行爲。

__ gt__(self, other)

定義了大於操做符,> 的行爲。

__ le__(self, other)

定義了小於等於操做符,<=的行爲。

__ ge__(self, other)

定義了大於等於操做符,>= 的行爲。

舉一個例子,設想對單詞進行類定義。咱們可能但願可以按內部對 string 的默認比較行爲,即字典序(經過字母)來比較單詞,也但願可以基於某些其餘的準則,像是長度或音節數。在本例中,咱們經過單詞長度排序,如下給出實現:

class Word(str):
'''單詞類,比較定義是基於單詞長度的'''

def __new__(cls, word):
# 注意,咱們使用了__new__,這是由於str是一個不可變類型,
# 因此咱們必須更早地初始化它(在建立時)
if ' ' in word:
print "單詞內含有空格,截斷到第一部分"
word = word[:word.index(' ')] # 在出現第一個空格以前全是字符瞭如今
return str.__new__(cls, word)

def __gt__(self, other):
return len(self) > len(other)
def __lt__(self, other):
return len(self) < len(other)
def __ge__(self, other):
return len(self) >= len(other)
def __le__(self, other):
return len(self) <= len(other)

如今,咱們能夠建立 2 個單詞(經過 Word('foo') 和 Word('bar'))並基於它們的長度進行比較了。注意,咱們沒有定義 __ eq__ 和 __ ne__。這是由於這可能致使某些怪異的行爲(特別是當比較 Word('foo') == Word('bar') 將會獲得 True 的結果)。基於單詞長度的相等比較會使人摸不清頭腦,所以咱們就沿用了str 自己的相等比較的實現。

如今多是一個好時機來提醒你一下,你沒必要重載每個比較相關的神奇方法來得到各類比較。標準庫已經友好地爲咱們在模板 functools 中提供了一個裝飾(decorator)類,定義了全部比較方法。你能夠只重載 __ eq__ 和一個其餘的方法(好比 __ gt__, lt,等)。這個特性只在 Python2.7(後?)適用,但當你有機會的話應該嘗試一下,它會爲你省下大量的時間和麻煩。你能夠經過在你本身的重載方法在加上 @total_ordering 來使用。

3.2 神奇方法——數字

就像你能夠經過重載比較操做符的途徑來建立你本身的類實例,你一樣能夠重載數字操做符。繫好大家的安全帶,朋友們,還有不少呢。處於本文組織的須要,我會把數字的神奇方法分割成5塊:一元操做符,常規算術操做符,反射算術操做符,增量賦值,類型轉換。

一元操做符

一元運算和函數僅有一個操做數,好比負數,絕對值等

__ pos__(self)

實現一元正數的行爲(如:+some_object)

__ neg__(self)

實現負數的行爲(如: -some_object)

__ abs__(self)

實現內建 abs() 函數的行爲

__ invert__(self)

實現用~操做符進行的取反行爲。你能夠參考 Wiki:bitwise operations 來解釋這個運算符究竟會幹什麼
常規算術操做符

如今咱們涵蓋了基本的二元運算符:+,-,* 等等。其中大部分都是不言自明的。

__ add__(self, other)

實現加法

__ sub__(self, other)

實現減法

__ mul__(self, other)

實現乘法

__ floordiv__(self, other)

實現地板除法,使用 // 操做符

__ div__(self, other)

實現傳統除法,使用 / 操做符

__ truediv__(self, other)

實現真正除法。注意,只有當你 from __ future__ import division 時纔會有效

__ mod__(self, other)

實現求模,使用 % 操做符

__ divmod__(self, other)

實現內建函數 divmod() 的行爲

__ pow__(self, other)

實現乘方,使用 ** 操做符

__ lshift__(self, other)

實現左按位位移,使用 << 操做符

__ rshift__(self, other)

實現右按位位移,使用 >> 操做符

__ and__(self, other)

實現按位與,使用 & 操做符

__ or__(self, other)

實現按位或,使用 | 操做符

__ xor__(self, other)

實現按位異或,使用 ^ 操做符

反射算術操做符

你知道我會如何解釋反射算術操做符?大家中的有些人或許會以爲它很大,很可怕,是國外的概念。但它實際上很簡單,下面給一個例子:

some_object + other

這是「常規的」加法。而反射其實至關於一回事,除了操做數改變了改變下位置:

other + some_object

所以,全部這些神奇的方法會作一樣的事等價於常規算術操做符,除了改變操做數的位置關係,好比第一個操做數和自身做爲第二個。此外沒有其餘的操做方式。在大多數狀況下,反射算術操做的結果等價於常規算術操做,因此你盡能夠在剛重載完 __ radd__就調用 add。乾脆痛快:

__radd__(self, other)

實現反射加法

__rsub__(self, other)

實現反射減法

__rmul__(self, other)

實現反射乘法

__rfloordiv__(self, other)

實現反射地板除,用 // 操做符

__rdiv__(self, other)

實現傳統除法,用 / 操做符

__rturediv__(self, other)

實現真實除法,注意,只有當你 from __future__ import division 時纔會有效

__rmod__(self, other)

實現反射求模,用 % 操做符

__rdivmod__(self, other)

實現內置函數 divmod() 的長除行爲,當調用 divmod(other,self) 時被調用

__rpow__(self, other)

實現反射乘方,用 ** 操做符

__rlshift__(self, other)

實現反射的左按位位移,使用 << 操做符

__rrshift__(self, other)

實現反射的右按位位移,使用 >> 操做符

__rand__(self, other)

實現反射的按位與,使用 & 操做符

__ror__(self, other)

實現反射的按位或,使用 | 操做符

__rxor__(self, other)

實現反射的按位異或,使用 ^ 操做符

增量賦值

Python 也有各類各樣的神奇方法容許用戶自定義增量賦值行爲。你可能已經熟悉增量賦值,它結合了「常規的」操做符和賦值。若是你仍不明白我在說什麼,下面有一個例子:

x = 5
x += 1 # 等價 x = x + 1

這些方法都不會有返回值,由於賦值在 Python 中不會有任何返回值。反而它們只是改變類的狀態。列表以下:

__iadd__(self, other)

實現加法和賦值

__isub__(self, other)

實現減法和賦值

__imul__(self, other)

實現乘法和賦值

__ifloordiv__(self, other)

實現地板除和賦值,用 //= 操做符

__idiv__(self, other)

實現傳統除法和賦值,用 /= 操做符

__iturediv__(self, other)

實現真實除法和賦值,注意,只有當你 from __future__ import division 時纔會有效

__imod__(self, other)

實現求模和賦值,用 %= 操做符

__ipow__(self, other)

實現乘方和賦值,用 **= 操做符

__ilshift__(self, other)

實現左按位位移和賦值,使用 <<= 操做符

__irshift__(self, other)

實現右按位位移和賦值,使用 >>= 操做符

__iand__(self, other)

實現按位與和賦值,使用 &= 操做符

__ior__(self, other)

實現按位或和賦值,使用 |= 操做符

__ixor__(self, other)

實現按位異或和賦值,使用 ^= 操做符

類型轉換的神奇方法

Python 也有一組神奇方法被設計用來實現內置類型轉換函數的行爲,如 float()

__int__(self)

實現到 int 的類型轉換

__long__(self)

實現到 long 的類型轉換

__float__(self)

實現到 float 的類型轉換

__complex__(self)

實現到複數的類型轉換

__oct__(self)

實現到 8 進制的類型轉換

__hex__(self)

實現到 16 進制的類型轉換

__index__(self)

實現一個當對象被切片到 int 的類型轉換。若是你自定義了一個數值類型,考慮到它可能被切片,因此你應該重載__index__

__trunc__(self)

當 math.trunc(self) 被調用時調用。__trunc__ 應當返回一個整型的截斷,(一般是 long)

__coerce__(self, other)

該方法用來實現混合模式的算術。若是類型轉換不可能那 __coerce__ 應當返回 None。 不然,它應當返回一對包含 self 和 other(2 元組),且調整到具備相同的類型

4.描述你的類

用一個字符串來講明一個類這一般是有用的。 在 Python 中提供了一些方法讓你能夠在你本身的類中自定義內建函數返回你的類行爲的描述。

__str__(self)

當你定義的類中一個實例調用了 str(),用於給它定義行爲

__repr__(self)

當你定義的類中一個實例調用了 repr(),用於給它定義行爲。 str() 和 repr() 主要的區別在於它的閱讀對象。 repr() 產生的輸出主要爲計算機可讀(在不少狀況下,這甚至多是一些有效的 Python 代碼),而 str() 則是爲了讓人類可讀。

__unicode__(self)

當你定義的類中一個實例調用了 unicode(),用於給它定義行爲。 unicode() 像是 str(),只不過它返回一個 unicode 字符串。 警戒!若是用戶用你的類中的一個實例調用了 str(),而你僅定義了 __unicode__(),那它是不會工做的。 以防萬一,你應當老是定義好 __str__(),哪怕用戶不會使用 unicode

__hash__(self)

當你定義的類中一個實例調用了 hash(),用於給它定義行爲。 它必須返回一個整型,並且它的結果是用於來在字典中做爲快速鍵比對。

__nonzero__(self)

當你定義的類中一個實例調用了 bool(),用於給它定義行爲。 返回 True 或 False,取決於你是否考慮一個實例是 True 或 False 的。

咱們已經至關漂亮地幹完了神奇方法無聊的部分(無示例),至此咱們已經討論了一些基礎的神奇方法,是時候讓咱們向高級話題移動了。

5.屬性訪問控制

有許多從其餘語言陣營轉到 Python 來的人抱怨 Python 對類缺少真正的封裝(好比,沒有辦法自定義 private 屬性,已經給出 public 的 getter 和 setter)。 這可不是真相喲:Python 經過神奇的方法實現了大量的封裝,而不是經過明確的方法或字段修飾符。

請看:

__ getattr__(self, name)

你能夠爲用戶在試圖訪問不存在(不管是存在或還沒有創建)的類屬性時定義其行爲。 這對捕捉和重定向常見的拼寫錯誤,給出使用屬性警告是有用的(只要你願意,你仍舊可選計算,返回那個屬性)或拋出一個 AttributeError異常。 這個方法只適用於訪問一個不存在的屬性,因此,這不算一個真正封裝的解決之道。

__ setattr__(self, name, value)

不像 __ getattr__, setattr 是一個封裝的解決方案。 它容許你爲一個屬性賦值時候的行爲,不論這個屬性是否存在。 這意味着你能夠給屬性值的任意變化自定義規則。 然而,你須要在乎的是你要當心使用 setattr,在稍後的列表中會做爲例子給出。

__ delattr__

這等價於 __ setattr__, 可是做爲刪除類屬性而不是 set 它們。 它須要相同的預防措施,就像 setattr,防止無限遞歸(當在 delattr 中調用 del self.name 會引發無限遞歸)。

__ getattribute__(self, name)

__ getattribute__ 良好地適合它的同伴們 __ setattr__ 和 __ delattr__。 可我卻不建議你使用它。 getattribute 只能在新式類中使用(在 Python 的最新版本中,全部的類都是新式類,在稍舊的版本中你能夠經過繼承 object 類來建立一個新式類。 它容許你定規則,在任什麼時候候無論一個類屬性的值那時候是否可訪問的。) 它會由於他的同伴中的出錯連坐受到某些無限遞歸問題的困擾(這時你能夠經過調用基類的 getattribute 方法來防止發生)。 當 getattribute 被實現而又只調用了該方法若是 getattribute 被顯式調用或拋出一個 AttributeError 異常,同時也主要避免了對 getattr 的依賴。 這個方法可使用(畢竟,這是你本身的選擇),不過我不推薦它是由於它有一個小小的用例(雖然說比較少見,但咱們須要特殊行爲以獲取一個值而不是賦值)以及它真的很難作到實現 0bug。

你能夠很容易地在你自定義任何類屬性訪問方法時引起一個問題。參考這個例子:

def __setattr__(self, name, value):
self.name = value
# 當每次給一個類屬性賦值時,會調用__setattr__(),這就造成了遞歸
# 由於它真正的含義是 self.__setattr__('name', value)
# 因此這方法不停地調用它本身,變成了一個沒法退出的遞歸最終引起crash

def __setattr__(self, name, value):
self.__dict__[name] = value # 給字典中的name賦值
# 在此自定義行爲

再一次,Python 的神奇方法向咱們展現了其難以置信的能力,同時巨大的力量也伴隨着重大的責任。 重要的是讓你明白正確使用神奇方法,這樣你就不會破壞其餘代碼。

那麼,咱們在關於定製類屬性訪問中學習了什麼? 不要輕易地使用,事實上它過於強大以及反直覺。 這也是它爲什麼存在的理由:Python 尋求幹壞事的可能性,但會把它們弄得很難。 自由是至高無上的,因此你能夠作任何你想作的事情。 如下是一個關於特殊屬性訪問方法的實際例子(注意,咱們使用 super 由於並不是全部類都有 __ dict__類屬性):

class AccessCounter:
'''一個類包含一個值和實現了一個訪問計數器。
當值每次發生變化時,計數器+1'''

def __init__(self, val):
super(AccessCounter, self).__setattr__('counter',0)
super(AccessCounter, self).__setattr__('value', val)

def __setattr__(self, name, value):
if name == 'value':
super(AccessCounter, self).__setattr__('counter', self.counter + 1)
# Make this unconditional.
# 若是你想阻止其餘屬性被建立,拋出AttributeError(name)異常
super(AccessCounter, self).__setattr__(name, value)

def __delattr__(self, name)
if name == 'value':
super(AccessCounter, self).__setattr__('counter', self.counter + 1)
super(AccessCounter, self).__delattr__(name)

6.製做自定義序列

頗有多種方式可讓你的類表現得像內建序列(字典,元組,列表,字符串等)。 這些是我迄今爲止最喜歡的神奇方法了,由於不合理的控制它們賦予了你一種魔術般地讓你的類實例整個全局函數數組漂亮工做的方式。 在咱們開始講解這個內容以前,讓咱們先快速理清需求。
需求

如今咱們正在談論如何建立你本身的序列。 也是什麼談一談 protocol 了。 protocol 在某些地方跟接口很類似。 接口在其餘語言中,是一組給定的方法,而你必須定義它們。 然而,在 Python 中 protocol 是徹底非正式的,並且不要求顯式聲明去實現。 更進一步說,它們更像是準則。

爲什麼咱們如今要談論 protocol? 由於在 Python 中要實現自定義容器類型會涉及使用到這其中某些 protocol。

首先,有一個 protocol 是爲定義不可變容器的:爲了製做一個不可變容器,你只須要定義 __ len__ 和__ getitem__(稍後詳述)。 可變容器 protocol 要求全部不可變容器增長 setitem delitem。 而後,若是你但願你的對象是可迭代的,那你還得定義一個會返回迭代器 iterator 的 iter 方法。 而且這個迭代器必須遵照一個迭代 protocol,也就是要求迭代器有回調方法 iter (返回自身)和 next。
隱藏在容器背後的魔法

已經火燒眉毛了?如下即是容器使用的神奇魔法:

__ len__(self)

返回容器的長度。部分 protocol 同時支持可變和不可變容器

__ getitem__(self, key)

定義當某一個 item 被訪問時的行爲,使用 self[key] 表示法。 這個一樣也是部分可變和不可變容器 protocol。 這也可拋出適當的異常: TypeError 當 key 的類型錯誤,或沒有值對應 Key 時。

__ setitem__(self, key, value)

定義當某一個 item 被賦值時候的行爲,使用 self[key]=value 表示法。 這也是部分可變和不可變容器 protocol。 再一次重申,你應當在適當之處拋出 KeyError 和 TypeError 異常。

__delitem__(self, key)

定義當某一個 item 被刪除(例如 del self[key])時的行爲。 這僅是部分可變容器的 protocol。在一個無效key 被使用後,你必須拋出一個合適的異常。

__ iter__(self)

應該給容器返回一個迭代器。 迭代器會返回若干內容,大多使用內建函數 iter() 表示。 當一個容器使用形如 for x in container: 的循環。 迭代器自己就是其對象,同時也要定義好一個 __iter__ 方法來返回自身。

__ reversed__(self)

當定義調用內建函數 reversed() 時的行爲。應該返回一個反向版本的列表。

__ contains__(self, item)

__ contains__ 爲成員關係,用 in 和 not in 測試時定義行爲。 那你會問這個爲什麼不是一個序列的 protocol 的一部分? 這是由於當 __contains__ 未定義,Python 就會遍歷序列,若是遇到正在尋找的 item 就會返回True。

__ concat__(self, other)

最後,你可經過 __concat__ 定義你的序列和另一個序列的鏈接。 應該從 self 和 other 返回一個新構建的序列。 當調用 2 個序列時 __concat__ 涉及操做符 +

一個例子

在咱們的例子中,讓咱們看一下一個 list 實現的某些基礎功能性的構建。 可能會讓你想起你使用的其餘語言(好比 Haskell)。

class FunctionalList:
'''類覆蓋了一個list的某些額外的功能性魔法,像head,
tail,init,last,drop,and take'''
def __init__(self, values=None):
if values is None:
self.values = []
else:
self.values = values

def __len__(self):
return len(self.values)

def __getitem__(self, key):
# 若是key是非法的類型和值,那麼list valuse會拋出異常
return self.values[key]

def __setitem__(self, key, value):
self.values[key] = value

def __delitem__(self, key):
del self.values[key]

def __iter__(self):
return iter(self.values)

def __reversed__(self):
return reversed(self.values)

def append(self, value):
self.values.append(value)
def head(self):
# 得到第一個元素
return self.values[0]
def tail(self):
# 得到在第一個元素後的其餘全部元素
return self.values[1:]
def init(self):
# 得到除最後一個元素的序列
return self.values[:-1]
def last(last):
# 得到最後一個元素
return self.values[-1]
def drop(self, n):
# 得到除前n個元素的序列
return self.values[n:]
def take(self, n):
# 得到前n個元素
return self.values[:n]

經過這個(輕量的)有用的例子你知道了如何實現你本身的序列。 固然,還有不少更有用的應用,可是它們其中的不少已經被標準庫實現了,像 Counter, OrderedDict, NamedTuple

7.反射

你也能夠經過定義神奇方法來控制如何反射使用內建函數 isinstance() 和 issubclass() 的行爲。 這些神奇方法是:

__instancecheck__(self, instance)

檢查一個實例是不是你定義類中的一個實例(好比,isinstance(instance, class))

__subclasscheck__(self, subclass)

檢查一個類是不是你定義類的子類(好比,issubclass(subclass, class))

這對於神奇方法的用例狀況來講可能較小,可它的確是真的。 我並不想花費太多的時間在反射方法上面,由於他們不是那麼地重要。 不過它們反映了在 Python 中關於面對對象編程一些重要的東西,並且在 Python 中的廣泛:老是在找一種簡單的方式來作某些事情,即便它能被用到的很少。 這些神奇方法彷佛看上去不那麼有用,但當你須要使用它們的時候你會感激它們的存在(和你閱讀的這本指南!)。

8.可調用對象

正如你可能已經知道,在 Python 中函數是第一類對象。 這就意味着它們能夠被傳遞到函數和方法,就像是任何類型的對象。 這真是一種難以置信強大的特性。

這是 Python 中一個特別的神奇方法,它容許你的類實例像函數。 因此你能夠「調用」它們,把他們當作參數傳遞給函數等等。 這是另外一個強大又便利的特性讓 Python 的編程變得更可愛了。

__ call__(self, [args...])

容許類實例像函數同樣被調用。 本質上,這意味着 x() 等價於 x.__ call__()。 注意, call 須要的參數數目是可變的,也就是說能夠對任何函數按你的喜愛定義參數的數目定義 call

__ call__ 可能對於那些常常改變狀態的實例來講是極其有用的。 「調用」實例是一種順應直覺且優雅的方式來改變對象的狀態。 下面一個例子是一個類表示一個實體在一個平面上的位置:

class Entity:
'''描述實體的類,被調用的時候更新實體的位置'''

def __init__(self, size, x, y):
self.x, self.y = x, y
self.size = size

def __call__(self, x, y):
'''改變實體的位置'''
self.x, self.y = x, y

#省略...

9.上下文管理

在 Python2.5 裏引入了一個新關鍵字(with)使得一個新方法獲得了代碼複用。 上下文管理這個概念在 Python 中早已不是新鮮事了(以前它做爲庫的一部分被實現過),但直到 PEP343 才做爲第一個類語言結構取得了重要地位而被接受。 你有可能早就已經見識過 with 聲明:

with open('foo.txt') as bar:
# 對bar執行某些動做

上下文管理容許對對象進行設置和清理動做,用 with 聲明進行已經封裝的操做。 上下文操做的行爲取決於 2 個神奇方法:

__ enter__(self)

定義塊用 with 聲明建立出來時上下文管理應該在塊開始作什麼。 注意,enter 的返回值必須綁定 with 聲明的目標,或是 as 後面的名稱。

__ exit__(self, exception_type, exception_value, traceback)

定義在塊執行(或終止)以後上下文管理應該作什麼。 它能夠用來處理異常,進行清理,或行動處於塊以後某些老是被當即處理的事。 若是塊執行成功的話,excepteion_type,exception_value,和 traceback 將會置None。 不然,你能夠選擇去處理異常,或者讓用戶本身去處理。 若是你想處理,確保在所有都完成以後__ exit__ 會返回 True。 若是你不想讓上下文管理處理異常,那就讓它發生好了。

__ enter__ 和 __ exit__ 對那些已有良好定義和對設置,清理行爲有共同行爲的特殊類是有用。 你也可使用這些方法去建立封裝其餘對象通用的上下文管理。 看下面的例子:

class Closer:
'''用with聲明一個上下文管理用一個close方法自動關閉一個對象'''

def __init__(self, obj):
self.obj = obj

def __enter__(self):
return self.obj # 綁定目標

def __exit__(self, exception_type, exception_val, trace):
try:
self.obj.close()
except AttributeError: #obj不具有close
print 'Not closable.'
return True # 成功處理異常

如下是一個對於 Closer 實際應用的一個例子,使用一個 FTP 鏈接進行的演示(一個可關閉的套接字):

>>> from magicmethods import Closer
>>> from ftplib import :;;
>>> with Closer(FTP('ftp.somsite.com')) as conn:
... conn.dir()
...
# 省略的輸出
>>> conn.dir()
# 一個很長的AttributeError消息, 不能關閉使用的一個鏈接
>>> with Closer(int(5)) as i:
... i += 1
...
Not closeable.
>>> i
6

瞧見咱們如何漂亮地封裝處理正確或不正確的用例了嗎?那就是上下文管理和神奇方法的威力。

10.構建描述符對象

描述符能夠改變其餘對象,也能夠是訪問類中任一的 getting,setting,deleting。 描述符不意味着孤立;相反,它們意味着會被它們的全部者類控制。 當創建面向對象數據庫或那些擁有相互依賴的屬性的類時,描述符是有用的。 當描述符在幾個不一樣單元或描述計算屬性時顯得更爲有用。

做爲一個描述符,一個類必須至少實現 __ get__, set,和 __delete__中的一個。 讓咱們快點看一下這些神奇方法吧:

__ get__(self, instance, owner)

當描述符的值被取回時定義其行爲。instance 是 owner 對象的一個實例,owner 是全部類。

__ set__(self, instance, value)

當描述符的值被改變時定義其行爲。instance 是 owner 對象的一個實例,value 是設置的描述符的值

__ delete__(self, instance)

當描述符的值被刪除時定義其行爲。instance 是 owner 對象的一個實例。

如今,有一個有用的描述符應用例子:單位轉換策略

class Meter(object):
'''米描述符'''

def __init__(self, value=0.0):
self.value = float(value)
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
self.value = float(value)

class Foot(object):
'''英尺描述符'''

def __get__(self, instance, owner):
return instance.meter * 3.2808
def __set__(self, instance, value):
instance.meter = float(value) / 3.2808

class Distance(object):
'''表示距離的類,控制2個描述符:feet和meters'''
meter = Meter()
foot = Foot()

11.Pickling 你的對象

假如你花時間和其餘 Pythonistas 打交道,那麼你至少有可能聽到過 Pickling 這個詞。 Pickling 是一種對 Python 數據結構的序列化過程。 若是你須要存儲一個對象,以後再取回它(一般是爲了緩存)那麼它就顯得格外地有用了。 同時,它也是產生憂慮和困惑的主要來源。

Pickling 是那麼地重要以致於它不只有本身專屬的模塊(pickle),還有本身的 protocol 和神奇方法與其相伴。 但首先用簡要的文字來解釋下如何 pickle 已經存在的類型(若是你已經懂了能夠隨意跳過這部份內容)
Pickling:鹽水中的快速浸泡

讓咱們跳入 pickling。 話說你有一個詞典你想要保存它並在稍後取回。 你能夠把它的內容寫到一個文件中去,須要很是當心地確保你寫了正確的語法,而後用 exec() 或處理文件的輸入取回寫入的內容。 但這是不穩定的:若是你你在純文本中保存重要的數據,它有可能被幾種方法改變,致使你的程序 crash 或在你的計算機上運行了惡意代碼而出錯。 因而,咱們準備 pickle 它:

import pickle

data = {'foo': [1,2,3],
'bar': ('Hello','world!'),
'baz': True}
jar = open('data.pk1', 'wb')
pickle.dump(data, jar) # 把pickled數據寫入jar文件
jar.close()

好了如今,已通過去了幾個小時。 咱們但願拿回數據,而咱們須要作的事僅僅是 unpickle 它:

import pickle

pk1_file = open('data.pk1','rb') #鏈接pickled數據
data = pickle.load(pk1_file) #把數據load到一個變量中去
print data
pk1_file.close()

發生了什麼事?正如你的預期,咱們得到了 data。

如今,我要給你一些忠告:pickling 並不是完美。 Pickle 文件很容易因意外或出於故意行爲而被損毀。 Pickling 可能比起使用純文本文件安全些,但它仍舊有可能會被用來跑惡意代碼。 還有由於 Python 版本的不兼容問題,因此不要指望發佈 Pickled 對象,也不要指望人們可以打開它們。 可是,它依然是一個強大的緩存工具和其餘常見序列化任務。

Pickling你自定義的對象

Pickling 不只可用在內建類型上,還能夠用於遵照 pickle 協議的任何類。 pickle 協議有 4 個可選方法用於定製 Python 對象如何運行(這跟 C 擴展有點不一樣,但那不在咱們討論的範圍內):

__ getinitargs__(self)

若是你想當你的類 unpickled 時調用 __ init__,那你能夠定義__ getinitargs__,該方法應該返回一個元組的參數,而後你能夠把他傳遞給 __ init__。注意,該方法僅適用於舊式類。

__ getnewargs__(self)

對於新式類,你能夠影響有哪些參數會被傳遞到 __new__ 進行 unpickling。 該方法一樣應該返回一個元組參數,而後能傳遞給 __new__

__getstate__(self)

代替對象的 __dict__ 屬性被保存。 當對象 pickled,你可返回一個自定義的狀態被保存。 當對象 unpickled 時,這個狀態將會被 __setstate__ 使用。

__setstate__(self, state)

對象 unpickled 時,若是 __setstate__ 定義對象狀態會傳遞來代替直接用對象的 __dict__ 屬性。 這正好跟__getstate__ 手牽手:當兩者都被定義了,你能夠描述對象的 pickled 狀態,任何你想要的。

一個例子:

咱們的例子是 Slate 類,它會記憶它曾經的值和已經寫入的值。 然而,當這特殊的 slate 每一次 pickle 都會被清空:當前值不會被保存。

import time

class Slate:
'''存儲一個字符串和一個變動log,當Pickle時會忘記它的值'''

def __init__(self, value):
self.value = value
self.last_change = time.asctime()
self.history = {}

def change(self, new_value):
# 改變值,提交最後的值到歷史記錄
self.history[self.last_change] = self.value
self.value = new_value
self.last_change = time.asctime()

def print_changes(self):
print 'Changelog for Slate object:'
for k, v in self.history.items():
print '%st %s' % (k, v)

def __getstate__(self):
# 故意不返回self.value 或 self.last_change.
# 當unpickle,咱們但願有一塊空白的"slate"
return self.history

def __setstate__(self, state):
# 讓 self.history = state 和 last_change 和 value被定義
self.history = state
self.value, self.last_change = None, None

12.總結

這份指南的目標就是任何人讀一讀它,無論讀者們是否具有 Python 或面對對象的編程經驗。 若是你正準備學習 Python,那你已經得到了編寫功能豐富,優雅,易用的類的寶貴知識。 若是你是一名中級 Python 程序員,你有可能已經拾起了一些新概念和策略和一些好的方法來減小你和你的用戶編寫的代碼量。 若是你是一名 Pythonista 專家,你可能已經回顧了某些你可能已經被你遺忘的知識點,或着你又學習到了一些新技巧。 無論你的的經驗等級,我但願此次 Python 神奇方法的旅程達到了真正神奇的效果。(我沒法控制本身在最後不用個雙關語)

附錄:若是調用神奇方法

Python 中的一些神奇方法直接映射到內建函數;在這種狀況下,調用它們的方法是至關明顯的。 然而,在其餘狀況下,那些調用方法就不這麼明顯了。 本附錄致力於揭開可以引導神奇方法被調用的非明顯語法。
神奇方法 調用方法 說明

`__new__(cls [,...])` `instance = MyClass(arg1, arg2)` `__new__` 在建立實例的時候被調用
`__init__(self [,...])` `instance = MyClass(arg1, arg2)` `__init__` 在建立實例的時候被調用
`__cmp__(self, other)` `self == other`, `self > other`, 等 在比較的時候調用
`__pos__(self)` `+self` 一元加運算符
`__neg__(self)` `-self` 一元減運算符
`__invert__(self)` `~self` 取反運算符
`__index__(self)` `x[self]` 對象被做爲索引使用的時候
`__nonzero__(self)` `bool(self)` 對象的布爾值
`__getattr__(self, name)` `self.name # name不存在` 訪問一個不存在的屬性時
`__setattr__(self, name, val)` `self.name = val` 對一個屬性賦值時
`__delattr__(self, name)` `del self.name` 刪除一個屬性時
`__getattribute(self, name)` `self.name` 訪問任何屬性時
`__getitem__(self, key)` `self[key]` 使用索引訪問元素時
`__setitem__(self, key, val)` `self[key] = val` 對某個索引值賦值時
`__delitem__(self, key)` `del self[key]` 刪除某個索引值時
`__iter__(self)` `for x in self` 迭代時
`__contains__(self, value)` `value in self`, `value not in self` 使用 `in` 操做測試關係時
`__concat__(self, value)` `self + other` 鏈接兩個對象時
`__call__(self [,...])` `self(args)` 「調用」對象時
`__enter__(self)` `with self as x:` `with` 語句上下文管理
`__exit__(self, exc, val, trace)` `with self as x:` `with` 語句上下文管理
`__getstate__(self)` `pickle.dump(pkl_file, self)` 序列化
`__setstate__(self)` `data = pickle.load(pkl_file)` 序列化

但願這張表格能夠幫你掃清你有關語法涉及到神奇方法的問題。

相關文章
相關標籤/搜索