一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

Python 魔術(shù)方法指南 — PyCoder's Weelky CN

 文檔集成 2014-01-03

Python 魔術(shù)方法指南?

  • 入門

  • 構(gòu)造和初始化

  • 構(gòu)造定制類
    • 用于比較的魔術(shù)方法
    • 用于數(shù)值處理的魔術(shù)方法
  • 表現(xiàn)你的類

  • 控制屬性訪問

  • 創(chuàng)建定制序列

  • 反射

  • 可以調(diào)用的對象

  • 會(huì)話管理器

  • 創(chuàng)建描述器對象

  • 持久化對象

  • 總結(jié)

  • 附錄

介紹?

此教程為我的數(shù)篇文章中的一個(gè)重點(diǎn)。主題是魔術(shù)方法。 什么是魔術(shù)方法?他們是面向?qū)ο蟮腜ython的一切。他們是可以給你的類增加”magic”的特殊方法。他們總是被雙下劃線所包圍(e.g. __init__ 或者 __lt__)。然而他們的文檔卻遠(yuǎn)沒有提供應(yīng)該有的內(nèi)容。Python中所有的魔術(shù)方法均在Python官方文檔中有相應(yīng)描述,但是對于他們的描述比較混亂而且組織比較松散。很難找到有一個(gè)例子(也許他們原本打算的很好,在開始語言參考中有描述很詳細(xì),然而隨之而來的確是枯燥的語法描述等等)。

所以,為了修補(bǔ)我認(rèn)為Python文檔應(yīng)該修補(bǔ)的瑕疵,我決定給Python中的魔術(shù)方法提供一些用平淡的語言和實(shí)例驅(qū)使的文檔。我在開始已經(jīng)寫了數(shù)篇博文,現(xiàn)在在這篇文章中對他們進(jìn)行總結(jié)。

我希望你能夠喜歡這篇文章。你可以將之當(dāng)做一個(gè)教程,一個(gè)補(bǔ)習(xí)資料,或者一個(gè)參考。本文章的目的僅僅是為Python中的魔術(shù)方法提供一個(gè)友好的教程。

構(gòu)造和初始化?

每個(gè)人都知道一個(gè)最基本的魔術(shù)方法, __init__ 。通過此方法我們可以定義一個(gè)對象的初始操作。然而,當(dāng)我調(diào)用 x = SomeClass() 的時(shí)候, __init__ 并不是第一個(gè)被調(diào)用的方法。實(shí)際上,還有一個(gè)叫做 __new__ 的方法,來構(gòu)造這個(gè)實(shí)例。然后給在開始創(chuàng)建時(shí)候的初始化函數(shù)來傳遞參數(shù)。在對象生命周期的另一端,也有一個(gè) __del__ 方法。我們現(xiàn)在來近距離的看一看這三個(gè)方法:

__new__(cls, [...) __new__ 是在一個(gè)對象實(shí)例化的時(shí)候所調(diào)用的第一個(gè)方法。它的第一個(gè)參數(shù)是這個(gè)類,其他的參數(shù)是用來直接傳遞給 __init__ 方法。 __new__ 方法相當(dāng)不常用,但是它有自己的特性,特別是當(dāng)繼承一個(gè)不可變的類型比如一個(gè)tuple或者string。我不希望在 __new__ 上有太多細(xì)節(jié),因?yàn)椴⒉皇呛苡杏锰帲窃?Python文檔 中有詳細(xì)的闡述。

__init__(self, […) 此方法為類的初始化方法。當(dāng)構(gòu)造函數(shù)被調(diào)用的時(shí)候的任何參數(shù)都將會(huì)傳給它。(比如如果我們調(diào)用 x = SomeClass(10, 'foo')),那么 __init__ 將會(huì)得到兩個(gè)參數(shù)10和foo。 __init__ 在Python的類定義中被廣泛用到。

__del__(self) 如果 __new____init__ 是對象的構(gòu)造器的話,那么 __del__ 就是析構(gòu)器。它不實(shí)現(xiàn)語句 del x (所以代碼將不會(huì)翻譯為 x.__del__() )。它定義的是當(dāng)一個(gè)對象進(jìn)行垃圾回收時(shí)候的行為。當(dāng)一個(gè)對象在刪除的時(shí)候需要更多的清潔工作的時(shí)候此方法會(huì)很有用,比如套接字對象或者是文件對象。注意,因?yàn)楫?dāng)解釋器退出的時(shí)候如果對象還存在,不能保證 __del__ 能夠被執(zhí)行,所以 __del__ can’t serve as a replacement for good coding practices ()~~~~~~~

放在一起的話,這里是一個(gè) __init____del__ 實(shí)際使用的例子。

from os.path import join

class FileObject:
    '''給文件對象進(jìn)行包裝從而確認(rèn)在刪除時(shí)文件流關(guān)閉'''

    def __init__(self, filepath='~', filename='sample.txt'):
        #讀寫模式打開一個(gè)文件
        self.file = open(join(filepath, filename), 'r+')

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

讓定制的類工作起來?

使用Python的魔術(shù)方法的最大優(yōu)勢在于他們提供了一種簡單的方法來讓對象可以表現(xiàn)的像內(nèi)置類型一樣。那意味著你可以避免丑陋的,違反直覺的,不標(biāo)準(zhǔn)的的操作方法。在一些語言中,有一些操作很常用比如:

if instance.equals(other_instance):
    # do something

在Python中你可以這樣。但是這會(huì)讓人迷惑且產(chǎn)生不必要的冗余。相同的操作因?yàn)椴煌膸鞎?huì)使用不同的名字,這樣會(huì)產(chǎn)生不必要的工作。然而有了魔術(shù)方法的力量,我們可以定義一個(gè)方法(本例中為 __eq__ ),就說明了我們的意思:

if instance == other_instance:
        #do something

這只是魔術(shù)方法的功能的一小部分。它讓你可以定義符號的含義所以我們可以在我們的類中使用。就像內(nèi)置類型一樣。

用于比較的魔術(shù)方法?

Python對實(shí)現(xiàn)對象的比較,使用魔術(shù)方法進(jìn)行了大的逆轉(zhuǎn),使他們非常直觀而不是笨拙的方法調(diào)用。而且還提供了一種方法可以重寫Python對對象比較的默認(rèn)行為(通過引用)。以下是這些方法和他們的作用。

__cmp__(self, other) __cmp__ 是最基本的用于比較的魔術(shù)方法。它實(shí)際上實(shí)現(xiàn)了所有的比較符號(<,==,!=,etc.),但是它的表現(xiàn)并不會(huì)總是如你所愿(比如,當(dāng)一個(gè)實(shí)例與另一個(gè)實(shí)例相等是通過一個(gè)規(guī)則來判斷,而一個(gè)實(shí)例大于另外一個(gè)實(shí)例是通過另外一個(gè)規(guī)則來判斷)。如果 self < other 的話 __cmp__ 應(yīng)該返回一個(gè)負(fù)數(shù),當(dāng) self == o 的時(shí)候會(huì)返回0 ,而當(dāng) self > other 的時(shí)候會(huì)返回正數(shù)。通常最好的一種方式是去分別定義每一個(gè)比較符號而不是一次性將他們都定義。但是 __cmp__ 方法是你想要實(shí)現(xiàn)所有的比較符號而一個(gè)保持清楚明白的一個(gè)好的方法。

__eq__(self, other) 定義了等號的行為, ==

__ne__(self, other) 定義了不等號的行為, != 。

__lt__(self, other) 定義了小于號的行為, <

__gt__(self, other) 定義了大于等于號的行為, >= 。

舉一個(gè)例子,創(chuàng)建一個(gè)類來表現(xiàn)一個(gè)詞語。我們也許會(huì)想要比較單詞的字典序(通過字母表),通過默認(rèn)的字符串比較的方法就可以實(shí)現(xiàn),但是我們也想要通過一些其他的標(biāo)準(zhǔn)來實(shí)現(xiàn),比如單詞長度或者音節(jié)數(shù)量。在這個(gè)例子中,我們來比較長度實(shí)現(xiàn)。以下是實(shí)現(xiàn)代碼:

class Word(str):
'''存儲(chǔ)單詞的類,定義比較單詞的幾種方法'''

    def __new__(cls, word):
        # 注意我們必須要用到__new__方法,因?yàn)閟tr是不可變類型
        # 所以我們必須在創(chuàng)建的時(shí)候?qū)⑺跏蓟?        if ' ' in word:
            print "Value contains spaces. Truncating to first space."
            word = word[:word.index(' ')] #單詞是第一個(gè)空格之前的所有字符
        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)

現(xiàn)在,我們創(chuàng)建兩個(gè) Words 對象(通過使用 Word('foo')Word('bar') 然后通過長度來比較它們。注意,我們沒有定義 __eq____ne__ 方法。這是因?yàn)閷?huì)產(chǎn)生一些怪異的結(jié)果(比如 Word('foo') == Word('bar') 將會(huì)返回true)。這對于測試基于長度的比較不是很有意義。所以我們退回去,用 str 內(nèi)置來進(jìn)行比較。

現(xiàn)在你知道你不必定義每一個(gè)比較的魔術(shù)方法從而進(jìn)行豐富的比較。標(biāo)準(zhǔn)庫中很友好的在 functiontols 中提供給我們一個(gè)類的裝飾器定義了所有的豐富的比較函數(shù)。如果你只是定義 __eq__ 和另外一個(gè)(e.g. __gt__, __lt__,etc.)這個(gè)特性僅僅在Python 2.7中存在,但是你如果有機(jī)會(huì)碰到的話,那么將會(huì)節(jié)省大量的時(shí)間和經(jīng)理。你可以通過在你定義的類前放置 @total_ordering 來使用。

數(shù)值處理的魔術(shù)方法?

如同你在通過比較符來比較類的實(shí)例的時(shí)候來創(chuàng)建很多方法,你也可以定義一些數(shù)值符號的特性。系緊你的安全帶,來吧,這里有很多內(nèi)容。為了組織方便,我將會(huì)把數(shù)值處理的方法來分成五類:一元操作符,普通算數(shù)操作符,反射算數(shù)操作符(之后會(huì)詳細(xì)說明),增量賦值,和類型轉(zhuǎn)換。

一元操作符和函數(shù)?

僅僅有一個(gè)操作位的一元操作符和函數(shù)。比如絕對值,負(fù)等。

__pos__(self) 實(shí)現(xiàn)正號的特性(比如 +some_object)

__neg__(self) 實(shí)現(xiàn)負(fù)號的特性(比如 -some_object)

__abs__(self) 實(shí)現(xiàn)內(nèi)置 abs() 函數(shù)的特性。

__invert__(self) 實(shí)現(xiàn) ~ 符號的特性。為了說明這個(gè)特性。你可以查看 Wikipedia中的這篇文章

普通算數(shù)操作符?

現(xiàn)在我們僅僅覆蓋了普通的二進(jìn)制操作符:+,-,*和類似符號。這些符號大部分來說都淺顯易懂。

__add__(self, other) 實(shí)現(xiàn)加法。 __sub__(self, other) 實(shí)現(xiàn)減法。 __mul__(self, other) 實(shí)現(xiàn)乘法。 __floordiv__(self, other) 實(shí)現(xiàn) // 符號實(shí)現(xiàn)的整數(shù)除法。 __div__(self, other) 實(shí)現(xiàn) / 符號實(shí)現(xiàn)的除法。 __truediv__(self, other) 實(shí)現(xiàn)真除法。注意只有只用了 from __future__ import division 的時(shí)候才會(huì)起作用。 __mod__(self, other) 實(shí)現(xiàn)取模算法 % __divmod___(self, other) 實(shí)現(xiàn)內(nèi)置 divmod() 算法 __pow__ 實(shí)現(xiàn)使用 ** 的指數(shù)運(yùn)算 __lshift__(self, other) 實(shí)現(xiàn)使用 << 的按位左移動(dòng) __rshift__(self, other) 實(shí)現(xiàn)使用 >> 的按位左移動(dòng) __and__(self, other) 實(shí)現(xiàn)使用 & 的按位與 __or__(self, other) 實(shí)現(xiàn)使用 | 的按位或 __xor__(self, other) 實(shí)現(xiàn)使用 ^ 的按位異或

反運(yùn)算?

下面我將會(huì)講解一些反運(yùn)算的知識。有些概念你可能會(huì)認(rèn)為恐慌或者是陌生。但是實(shí)際上非常簡單。以下是一個(gè)例子:

some_object + other

這是一個(gè)普通的加法運(yùn)算,反運(yùn)算是相同的,只是把操作數(shù)調(diào)換了位置:

other + some_object

所以,除了當(dāng)與其他對象操作的時(shí)候自己會(huì)成為第二個(gè)操作數(shù)之外,所有的這些魔術(shù)方法都與普通的操作是相同的。大多數(shù)情況下,反運(yùn)算的結(jié)果是與普通運(yùn)算相同的。所以你可以你可以將 __radd____add__ 等價(jià)。

__radd__(self, other) 實(shí)現(xiàn)反加 __rsub__(self, other) 實(shí)現(xiàn)反減 __rmul__(self, other) 實(shí)現(xiàn)反乘 __rfloordiv__(self, other) 實(shí)現(xiàn) // 符號的反除 __rdiv__(self, other) 實(shí)現(xiàn) / 符號的反除 __rtruediv__(self, other) 實(shí)現(xiàn)反真除,只有當(dāng) from __future__ import division 的時(shí)候會(huì)起作用 __rmod__(self, other) 實(shí)現(xiàn) % 符號的反取模運(yùn)算 __rdivmod__(self, other) 當(dāng) divmod(other, self) 被調(diào)用時(shí),實(shí)現(xiàn)內(nèi)置 divmod() 的反運(yùn)算 __rpow__ 實(shí)現(xiàn) ** 符號的反運(yùn)算 __rlshift__(self, other) 實(shí)現(xiàn) << 符號的反左位移 __rrshift__(self, other) 實(shí)現(xiàn) >> 符號的反右位移 __rand__(self, other) 實(shí)現(xiàn) & 符號的反與運(yùn)算 __ror__(self, other) 實(shí)現(xiàn) | 符號的反或運(yùn)算 __xor__(self, other) 實(shí)現(xiàn) ^ 符號的反異或運(yùn)算

增量賦值?

Python也有大量的魔術(shù)方法可以來定制增量賦值語句。你也許對增量賦值已經(jīng)很熟悉,它將操作符與賦值來結(jié)合起來。如果你仍然不清楚我在說什么的話,這里有一個(gè)例子:

x = 5
x += 1 # in other words x = x + 1

__iadd__(self, other) 實(shí)現(xiàn)賦值加法 __isub__(self, other) 實(shí)現(xiàn)賦值減法 __imul__(self, other) 實(shí)現(xiàn)賦值乘法 __ifloordiv__(self, other) 實(shí)現(xiàn) //= 的賦值地板除 __idiv__(self, other) 實(shí)現(xiàn)符號 /= 的賦值除 __itruediv__(self, other) 實(shí)現(xiàn)賦值真除,只有使用 from __future__ import division 的時(shí)候才能使用 __imod_(self, other) 實(shí)現(xiàn)符號 %= 的賦值取模 __ipow__ 實(shí)現(xiàn)符號 **= 的賦值冪運(yùn)算 __ilshift__(self, other) 實(shí)現(xiàn)符號 <<= 的賦值位左移 __irshift__(self, other) 實(shí)現(xiàn)符號 >>= 的賦值位右移 __iand__(self, other) 實(shí)現(xiàn)符號 &= 的賦值位與 __ior__(self, other) 實(shí)現(xiàn)符號 |= 的賦值位或 __ixor__(self, other) 實(shí)現(xiàn)符號 |= 的賦值位異或

類型轉(zhuǎn)換魔術(shù)方法?

Python也有很多的魔術(shù)方法來實(shí)現(xiàn)類似 float() 的內(nèi)置類型轉(zhuǎn)換特性。 __int__(self) 實(shí)現(xiàn)整形的強(qiáng)制轉(zhuǎn)換 __long__(self) 實(shí)現(xiàn)長整形的強(qiáng)制轉(zhuǎn)換 __float__(self) 實(shí)現(xiàn)浮點(diǎn)型的強(qiáng)制轉(zhuǎn)換 __complex__(self) 實(shí)現(xiàn)復(fù)數(shù)的強(qiáng)制轉(zhuǎn)換 __oct__(self) 實(shí)現(xiàn)八進(jìn)制的強(qiáng)制轉(zhuǎn)換 __hex__(self) 實(shí)現(xiàn)二進(jìn)制的強(qiáng)制轉(zhuǎn)換 __index__(self) 當(dāng)對象是被應(yīng)用在切片表達(dá)式中時(shí),實(shí)現(xiàn)整形強(qiáng)制轉(zhuǎn)換,如果你定義了一個(gè)可能在切片時(shí)用到的定制的數(shù)值型,你應(yīng)該定義 __index__ (詳見PEP357) __trunc__(self) 當(dāng)使用 math.trunc(self) 的時(shí)候被調(diào)用。 __trunc__ 應(yīng)該返回?cái)?shù)值被截取成整形(通常為長整形)的值 __coerce__(self, other) 實(shí)現(xiàn)混合模式算數(shù)。如果類型轉(zhuǎn)換不可能的話,那么 __coerce__ 將會(huì)返回 None ,否則他將對 selfother 返回一個(gè)長度為2的tuple,兩個(gè)為相同的類型。

表現(xiàn)你的類?

如果有一個(gè)字符串來表示一個(gè)類將會(huì)非常有用。在Python中,有很多方法可以實(shí)現(xiàn)類定義內(nèi)置的一些函數(shù)的返回值。 __str__(self) 定義當(dāng) str() 調(diào)用的時(shí)候的返回值 __repr__(self) 定義 repr() 被調(diào)用的時(shí)候的返回值。 str()repr() 的主要區(qū)別在于 repr() 返回的是機(jī)器可讀的輸出,而 str() 返回的是人類可讀的。 __unicode__(self) 定義當(dāng) unicode() 調(diào)用的時(shí)候的返回值。 unicode()str() 很相似,但是返回的是unicode字符串。注意,如a果對你的類調(diào)用 str() 然而你只定義了 __unicode__() ,那么將不會(huì)工作。你應(yīng)該定義 __str__() 來確保調(diào)用時(shí)能返回正確的值。

__hash__(self) 定義當(dāng) hash() 調(diào)用的時(shí)候的返回值,它返回一個(gè)整形,用來在字典中進(jìn)行快速比較 __nonzero__(self) 定義當(dāng) bool() 調(diào)用的時(shí)候的返回值。本方法應(yīng)該返回True或者False,取決于你想讓它返回的值。

控制屬性訪問?

許多從其他語言轉(zhuǎn)到Python的人會(huì)抱怨它缺乏類的真正封裝。(沒有辦法定義私有變量,然后定義公共的getter和setter)。Python其實(shí)可以通過魔術(shù)方法來完成封裝。我們來看一下:

__getattr__(self, name) 你可以定義當(dāng)用戶試圖獲取一個(gè)不存在的屬性時(shí)的行為。這適用于對普通拼寫錯(cuò)誤的獲取和重定向,對獲取一些不建議的屬性時(shí)候給出警告(如果你愿意你也可以計(jì)算并且給出一個(gè)值)或者處理一個(gè) AttributeError 。只有當(dāng)調(diào)用不存在的屬性的時(shí)候會(huì)被返回。然而,這不是一個(gè)封裝的解決方案。 __setattr__(self, name, value)__getattr__ 不同, __setattr__ 是一個(gè)封裝的解決方案。無論屬性是否存在,它都允許你定義對對屬性的賦值行為,以為這你可以對屬性的值進(jìn)行個(gè)性定制。但是你必須對使用 __setattr__ 特別小心。之后我們會(huì)詳細(xì)闡述。 __delattr____setattr__ 相同,但是功能是刪除一個(gè)屬性而不是設(shè)置他們。注意與 __setattr__ 相同,防止無限遞歸現(xiàn)象發(fā)生。(在實(shí)現(xiàn) __delattr__ 的時(shí)候調(diào)用 del self.name 即會(huì)發(fā)生) __getattribute__(self, name) __getattribute__ 與它的同伴 __setattr____delattr__ 配合非常好。但是我不建議使用它。只有在新類型類定義中才能使用 __getattribute__ (在最新版本Python中所有的類都是新類型,在老版本中你可以通過繼承 object 來制作一個(gè)新類。這樣你可以定義一個(gè)屬性值的訪問規(guī)則。有時(shí)也會(huì)產(chǎn)生一些帝歸現(xiàn)象。(這時(shí)候你可以調(diào)用基類的 __getattribute__ 方法來防止此現(xiàn)象的發(fā)生。)它可以消除對 __getattr__ 的使用,如果它被明確調(diào)用或者一個(gè) AttributeError 被拋出,那么當(dāng)實(shí)現(xiàn) __getattribute__ 之后才能被調(diào)用。此方法是否被使用其實(shí)最終取決于你的選擇。)我不建議使用它因?yàn)樗氖褂脦茁瘦^小(我們在取得一個(gè)值而不是設(shè)置一個(gè)值的時(shí)候有特殊的行為是非常罕見的。)而且它不能避免會(huì)出現(xiàn)bug。

在進(jìn)行屬性訪問控制定義的時(shí)候你可能會(huì)很容易的引起一個(gè)錯(cuò)誤??紤]下面的例子。

def __setattr__(self, name, value):
    self.name = value
    #每當(dāng)屬性被賦值的時(shí)候, ``__setattr__()`` 會(huì)被調(diào)用,這樣就造成了遞歸調(diào)用。
    #這意味這會(huì)調(diào)用 ``self.__setattr__('name', value)`` ,每次方法會(huì)調(diào)用自己。這樣會(huì)造成程序崩潰。

def __setattr__(self, name, value):
    self.__dict__[name] = value  #給類中的屬性名分配值
    #定制特有屬性

Python的魔術(shù)方法非常強(qiáng)大,然而隨之而來的則是責(zé)任。了解正確的方法去使用非常重要。

所以我們對于定制屬性訪問權(quán)限了解了多少呢。它不應(yīng)該被輕易的使用。實(shí)際上,它非常強(qiáng)大。但是它存在的原因是:Python 不會(huì)試圖將一些不好的東西變得不可能,而是讓它們難以實(shí)現(xiàn)。自由是至高無上的,所以你可以做任何你想做的。一下是一個(gè)特別的屬性控制的例子(我們使用 super 因?yàn)椴皇撬械念惗加?__dict__ 屬性):

class AccessCounter:
    '''一個(gè)包含計(jì)數(shù)器的控制權(quán)限的類每當(dāng)值被改變時(shí)計(jì)數(shù)器會(huì)加一'''

    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)
    #如果你不想讓其他屬性被訪問的話,那么可以拋出 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)]

創(chuàng)建定制的序列?

有很多方法讓你的Python類行為可以像內(nèi)置的序列(dict, tuple,list, string等等)。這是目前位置我最喜歡的魔術(shù)方法因?yàn)樗o你很搞的控制權(quán)限而且讓很多函數(shù)在你的類實(shí)例上工作的很出色。但是在開始之前,需要先講一些必須條件。

必須條件?

現(xiàn)在我們開始講如何在Python中創(chuàng)建定制的序列,這個(gè)時(shí)候該講一講協(xié)議。協(xié)議(Protocols)與其他語言中的接口很相似。它給你很多你必須定義的方法。然而在Python中的協(xié)議是很不正式的,不需要明確聲明實(shí)現(xiàn)。事實(shí)上,他們更像一種指南。

我們?yōu)槭裁船F(xiàn)在討論協(xié)議?因?yàn)槿绻ㄖ迫萜黝愋偷脑捫枰玫竭@些協(xié)議。首先,實(shí)現(xiàn)不變?nèi)萜鞯脑捰幸粋€(gè)協(xié)議:實(shí)現(xiàn)不可變?nèi)萜鳎阒荒芏x __len____getitem__ (一會(huì)會(huì)講更多)??勺?nèi)萜鲄f(xié)議則需要所有不可變?nèi)萜鞯乃辛硗膺€需要 __setitem____delitem__ 。最終,如果你希望你的對象是可迭代的話,你需要定義 __iter__ 會(huì)返回一個(gè)迭代器。迭代器必須遵循迭代器協(xié)議,需要有 __iter__ (返回它本身) 和 next 。

容器后的魔法?

這些是容器使用的魔術(shù)方法。 __len__(self) 然會(huì)容器長度。對于可變不可變?nèi)萜鞫夹枰械膮f(xié)議的一部分。 __getitem__(self, key) 定義當(dāng)一個(gè)條目被訪問時(shí),使用符號 self[key] 。這也是不可變?nèi)萜骱涂勺內(nèi)萜鞫家械膮f(xié)議的一部分。如果鍵的類型錯(cuò)誤和 KeyError 或者沒有合適的值。那么應(yīng)該拋出適當(dāng)?shù)?TypeError 異常。 __setitem__(self, key, value) 定義當(dāng)一個(gè)條目被賦值時(shí)的行為,使用 self[key] = value 。這也是可變?nèi)萜骱筒豢勺內(nèi)萜鲄f(xié)議中都要有的一部分。 __delitem__(self, key) 定義當(dāng)一個(gè)條目被刪除時(shí)的行為(比如 del self[key])。這只是可變?nèi)萜鲄f(xié)議中的一部分。當(dāng)使用一個(gè)無效的鍵時(shí)應(yīng)該拋出適當(dāng)?shù)漠惓!?__iter__(self) 返回一個(gè)容器的迭代器。很多情況下會(huì)返回迭代器,尤其是當(dāng)內(nèi)置的 iter() 方法被調(diào)用的時(shí)候,或者當(dāng)使用 for x in container 方式循環(huán)的時(shí)候。迭代器是他們本身的對象,他們必須定義返回 self__iter__ 方法。 __reversed__(self) 實(shí)現(xiàn)當(dāng) reversed() 被調(diào)用時(shí)的行為。應(yīng)該返回列表的反轉(zhuǎn)版本。 __contains__(self, item) 當(dāng)調(diào)用 innot in 來測試成員是否存在時(shí)候 __contains__ 被定義。你問為什么這個(gè)不是序列協(xié)議的一部分?那是因?yàn)楫?dāng) __contains__ 沒有被定義的時(shí)候,Python會(huì)迭代這個(gè)序列并且當(dāng)找到需要的值時(shí)會(huì)返回 True 。 __concat__(self, other) 最終,你可以通過 __concat__ 來定義當(dāng)用其他的來連接兩個(gè)序列時(shí)候的行為。當(dāng) + 操作符被調(diào)用時(shí)候會(huì)返回一個(gè) selfother.__concat__ 被調(diào)用后的結(jié)果產(chǎn)生的新序列。

一個(gè)例子?

在我們的例子中,讓我們看一看你可能在其他語言中 用到的函數(shù)構(gòu)造語句的實(shí)現(xiàn)(比如 Haskell)。

class FunctionalList:
'''一個(gè)封裝了一些附加魔術(shù)方法比如 head, tail, init, last, drop, 和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):
    #如果鍵的類型或者值無效,列表值將會(huì)拋出錯(cuò)誤
    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(self):
    #返回末尾元素
    return self.values[-1]
def drop(self, n):
    #返回除前n個(gè)外的所有元素
    return self.values[n:]
def take(self, n):
    #返回前n個(gè)元素
    return self.values[:n]

反射?

你可以通過魔術(shù)方法控制控制使用 isinstance()issubclass() 內(nèi)置方法的反射行為。這些魔術(shù)方法是:

__instancecheck__(self, instance)

檢查一個(gè)實(shí)例是不是你定義的類的實(shí)例

__subclasscheck__(self, subclass)

檢查一個(gè)類是不是你定義的類的子類

這些方法的用例似乎很少,這也許是真的。我不會(huì)花更多的時(shí)間在這些魔術(shù)方法上因?yàn)樗麄儾⒉皇呛苤匾?,但是他們的確反應(yīng)了Python 中的面向?qū)ο缶幊痰囊恍┗咎匦?非常容易的去做一些事情,即使并不是很必須。這些魔術(shù)方法看起來并不是很有用,但是當(dāng)你需要的時(shí)候你會(huì)很高興有這種特性。

可以調(diào)用的對象?

你也許已經(jīng)知道,在Python中,方法也是一種高等的對象。這以為著他們也可以被傳遞到方法中就像其他對象一樣。這是一個(gè)非常驚人的特性。 在Python中,一個(gè)特殊的魔術(shù)方法可以讓類的實(shí)例的行為表現(xiàn)的像函數(shù)一樣,你可以調(diào)用他們,將一個(gè)函數(shù)當(dāng)做一個(gè)參數(shù)傳到另外一個(gè)函數(shù)中等等。這是一個(gè)非常強(qiáng)大的特性讓Python編程更加舒適甜美。 __call__(self, [args...])

允許一個(gè)類的實(shí)例像函數(shù)一樣被調(diào)用。實(shí)質(zhì)上說,這意味著 x()x.__call__() 是相同的。注意 __call__ 參數(shù)可變。這意味著你可以定義 __call__ 為其他你想要的函數(shù),無論有多少個(gè)參數(shù)。

__call__ 在那些類的實(shí)例經(jīng)常改變狀態(tài)的時(shí)候會(huì)非常有效。調(diào)用這個(gè)實(shí)例是一種改變這個(gè)對象狀態(tài)的直接和優(yōu)雅的做法。用一個(gè)實(shí)例來表達(dá)最好不過了:

class Entity:
'''調(diào)用實(shí)體來改變實(shí)體的位置。'''

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

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

會(huì)話管理?

在Python 2.5中,為了代碼利用定義了一個(gè)新的關(guān)鍵詞 with 語句。會(huì)話控制在Python中不罕見(之前是作為庫的一部分被實(shí)現(xiàn)),直到 PEP343 被添加后。它被成為一級語言結(jié)構(gòu)。你也許之前看到這樣的語句:

with open('foo.txt') as bar:
# perform some action with bar

回話控制器通過包裝一個(gè) with 語句來設(shè)置和清理行為?;卦捒刂破鞯男袨橥ㄟ^兩個(gè)魔術(shù)方法來定義: __enter__(self) 定義當(dāng)使用 with 語句的時(shí)候會(huì)話管理器應(yīng)該初始塊被創(chuàng)建的時(shí)候的行為。注意 __enter__ 的返回值被 with 語句的目標(biāo)或者 as 后的名字綁定。 __exit__(self, exception_type, exception_value, traceback) 定義當(dāng)一個(gè)代碼塊被執(zhí)行或者終止后會(huì)話管理器應(yīng)該做什么。它可以被用來處理異常,清楚工作或者做一些代碼塊執(zhí)行完畢之后的日常工作。如果代碼塊執(zhí)行成功, exception_type , exception_value , 和 traceback 將會(huì)是 None 。否則的話你可以選擇處理這個(gè)異?;蛘呤侵苯咏唤o用戶處理。如果你想處理這個(gè)異常的話,確認(rèn) __exit__ 在所有結(jié)束之后會(huì)返回 True 。如果你想讓異常被會(huì)話管理器處理的話,那么就這樣處理。

__enter__exit__ 對于明確有定義好的和日常行為的設(shè)置和清潔工作的類很有幫助。你也可以使用這些方法來創(chuàng)建一般的可以包裝其他對象的會(huì)話管理器。以下是一個(gè)例子。

class Closer:
'''通過with語句和一個(gè)close方法來關(guān)閉一個(gè)對象的會(huì)話管理器'''

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

def __enter__(self):
    return self.obj # bound to target

def __exit__(self, exception_type, exception_val, trace):
    try:
        self.obj.close()
    except AttributeError: # obj isn't closable
        print 'Not closable.'
        return True # exception handled successfully

以下是一個(gè)使用 Closer 的例子,使用一個(gè)FTP鏈接來證明(一個(gè)可關(guān)閉的套接字):

>>> from magicmethods import Closer
>>> from ftplib import FTP
>>> with Closer(FTP('ftp.somesite.com')) as conn:
...     conn.dir()
...
>>> conn.dir()
>>> with Closer(int(5)) as i:
...     i += 1
...
Not closable.
>>> i
6

你已經(jīng)看到了我們的包裝器如何靜默的處理適當(dāng)和不適當(dāng)?shù)氖褂眯袨?。這是會(huì)話管理器和魔術(shù)方法的強(qiáng)大功能。

創(chuàng)建對象的描述器?

描述器是通過得到,設(shè)置,刪除的時(shí)候被訪問的類。當(dāng)然也可以修改其他的對象。描述器并不是鼓勵(lì)的,他們注定被一個(gè)所有者類所持有。當(dāng)創(chuàng)建面向?qū)ο蟮臄?shù)據(jù)庫或者類,里面含有相互依賴的屬性時(shí),描述器將會(huì)非常有用。一種典型的使用方法是用不同的單位表示同一個(gè)數(shù)值,或者表示某個(gè)數(shù)據(jù)的附加屬性(比如坐標(biāo)系上某個(gè)點(diǎn)包含了這個(gè)點(diǎn)到遠(yuǎn)點(diǎn)的距離信息)。

為了構(gòu)建一個(gè)描述器,一個(gè)類必須有至少 __get__ 或者 __set__ 其中一個(gè),并且 __delete__ 被實(shí)現(xiàn)。讓我們看看這些魔術(shù)方法。 __get__(self, instance, owner) 定義當(dāng)描述器的值被取得的時(shí)候的行為, instance 是擁有者對象的一個(gè)實(shí)例。 owner 是擁有者類本身。 __set__(self, instance, value) 定義當(dāng)描述器值被改變時(shí)候的行為。 instance 是擁有者類的一個(gè)實(shí)例 value 是要設(shè)置的值。 __delete__(self, instance) 定義當(dāng)描述器的值被刪除的行為。``instance`` 是擁有者對象的實(shí)例。 以下是一個(gè)描述器的實(shí)例:單位轉(zhuǎn)換。

class Meter(object):
'''Descriptor for a meter.'''

    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):
    '''Descriptor for a foot.'''

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

class Distance(object):
    '''Class to represent distance holding two descriptors for feet and
    meters.'''
    meter = Meter()
    foot = Foot()

儲(chǔ)存你的對象?

如果你接觸過其他的 Pythoner,你可能已經(jīng)聽說過 Pickle 了, Pickle 是用來序列化 Python 數(shù)據(jù)結(jié)構(gòu)的模塊,在你需要暫時(shí)存儲(chǔ)一個(gè)對象的時(shí)候(比如緩存),這個(gè)模塊非常的有用,不過這同時(shí)也是隱患的誕生地。

序列化數(shù)據(jù)是一個(gè)非常重要的功能,所以他不僅僅擁有相關(guān)的模塊( Pickle , cPickle ),還有自己的協(xié)議以及魔術(shù)方法,不過首先,我們先討論下關(guān)于序列化內(nèi)建數(shù)據(jù)結(jié)構(gòu)的方法。

Pickling: 簡單例子?

讓我們深入研究 Pickle,比如說你現(xiàn)在需要臨時(shí)儲(chǔ)存一個(gè)字典,你可以把它寫入到一個(gè)文件里,并且要小心翼翼的確保格式正確,之后再用 exec() 或者處理文件輸入來恢復(fù)數(shù)據(jù),實(shí)際上這是很不安全的,如果你使用文本存儲(chǔ)了一些重要的數(shù)據(jù),任何方式的改變都可能會(huì)影響到你的程序,輕則程序崩潰,重則被惡意程序利用,所以,讓我們用 Pickle 代替這種方式:

import pickle

data = {'foo': [1, 2, 3],
        'bar': ('Hello', 'world!'),
        'baz': True}
jar = open('data.pkl', 'wb')
pickle.dump(data, jar) # write the pickled data to the file jar
jar.close()

嗯,過了幾個(gè)小時(shí)之后,我們需要用到它了,只需把它 unpickle 了就行了:

import pickle

pkl_file = open('data.pkl', 'rb') # connect to the pickled data
data = pickle.load(pkl_file) # load it into a variable
print data
pkl_file.close()

正如你期望的,數(shù)據(jù)原封不動(dòng)的回來了!

同時(shí)要給你一句忠告: pickle 并不是很完美, Pickle 文件很容易被不小心或者故意損壞, Pickle 文件比純文本文件要稍微安全一點(diǎn),但是還是可以被利用運(yùn)行惡意程序。 Pickle 不是跨版本兼容的(譯注:最近剛好在 《Python Cookbook》上看到相關(guān)討論,書中描述的 Pickle 是跨版本兼容的,此點(diǎn)待驗(yàn)證),所以盡量不要去分發(fā) Pickle 過的文本,因?yàn)閯e人并不一定能夠打開。不過在做緩存或者其他需要序列化數(shù)據(jù)的時(shí)候, Pickle 還是很有用處的。

序列化你自己的對象?

Pickle 并不是只支持內(nèi)建數(shù)據(jù)結(jié)果,任何遵循 Pickle 協(xié)議的類都可以,Pickle 協(xié)議為 Python 對象規(guī)定了4個(gè)可選方法來自定義 Pickle 行為(對于 C 擴(kuò)展的 cPickle 模塊會(huì)有一些不同,但是這并不在我們的討論范圍內(nèi)):

__getinitargs__(self)

如果你希望在逆序列化的同時(shí)調(diào)用 __init__ ,你可以定義 __getinitargs__ 方法,這個(gè)方法應(yīng)該返回一系列你想被 __init__ 調(diào)用的參數(shù),注意這個(gè)方法只對老樣式的類起作用。

__getnewargs__(self)

對于新式的類,你可以定義任何在重建對象時(shí)候傳遞到 __new__ 方法中的參數(shù)。這個(gè)方法也應(yīng)該返回一系列的被 __new__ 調(diào)用的參數(shù)。

__getstate__(self)

你可以自定義當(dāng)對象被序列化時(shí)返回的狀態(tài),而不是使用 __dict 方法,當(dāng)逆序列化對象的時(shí)候,返回的狀態(tài)將會(huì)被 __setstate__ 方法調(diào)用。

__setstate__(self, state)

在對象逆序列化的時(shí)候,如果 __setstate__ 定義過的話,對象的狀態(tài)將被傳給它而不是傳給 __dict__ 。這個(gè)方法是和 __getstate__ 配對的,當(dāng)這兩個(gè)方法都被定義的時(shí)候,你就可以完全控制整個(gè)序列化與逆序列化的過程了。

例子?

我們以 Slate 為例,這是一段記錄一個(gè)值以及這個(gè)值是何時(shí)被寫入的程序,但是,這個(gè) Slate 有一點(diǎn)特殊的地方,當(dāng)前值不會(huì)被保存。

import time

class Slate:
    '''Class to store a string and a changelog, and forget its value when
    pickled.'''

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

    def change(self, new_value):
        # Change the value. Commit last value to history
        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 '%s\t %s' % (k, v)

    def __getstate__(self):
        # Deliberately do not return self.value or self.last_change.
        # We want to have a "blank slate" when we unpickle.
        return self.history

    def __setstate__(self, state):
        # Make self.history = state and last_change and value undefined
        self.history = state
        self.value, self.last_change = None, None

結(jié)論?

這份指南的希望為所有人都能帶來一些知識,即使你是 Python 大?;蛘邔τ诰ㄓ诿嫦?qū)ο箝_發(fā)。如果你是一個(gè) Python 初學(xué)者,閱讀這篇文章之后你已經(jīng)獲得了編寫豐富,優(yōu)雅,靈活的類的知識基礎(chǔ)了。如果你是一個(gè)有一些經(jīng)驗(yàn)的 Python 程序員,你可能會(huì)發(fā)現(xiàn)一些能讓你寫的代碼更簡潔的方法。如果你是一個(gè) Python 大牛,可能會(huì)幫助你想起來一些你已經(jīng)遺忘的知識,或者一些你還沒聽說過的新功能。不管你現(xiàn)在有多少經(jīng)驗(yàn),我希望這次對于 Python 特殊方法的旅程能夠帶給你一些幫助(用雙關(guān)語真的很不錯(cuò) XD)(譯注: 這里的雙關(guān)在于標(biāo)題為 Magic Methods 這里是 神奇的旅程 ,不過由于中英語序的問題,直譯略顯頭重腳輕,所以稍微變化了下意思,丟掉了雙關(guān)的含義)。

附錄:如何調(diào)用魔術(shù)方法?

一些魔術(shù)方法直接和內(nèi)建函數(shù)相對,在這種情況下,調(diào)用他們的方法很簡單,但是,如果是另外一種不是特別明顯的調(diào)用方法,這個(gè)附錄介紹了很多并不是很明顯的魔術(shù)方法的調(diào)用形式。

魔術(shù)方法 調(diào)用方式 解釋
__new__(cls [,...]) instance = MyClass(arg1, arg2) __new__ 在創(chuàng)建實(shí)例的時(shí)候被調(diào)用
__init__(self [,...]) instance = MyClass(arg1, arg2) __init__ 在創(chuàng)建實(shí)例的時(shí)候被調(diào)用
__cmp__(self, other) self == other, self > other, 等。 在比較的時(shí)候調(diào)用
__pos__(self) +self 一元加運(yùn)算符
__neg__(self) -self 一元減運(yùn)算符
__invert__(self) ~self 取反運(yùn)算符
__index__(self) x[self] 對象被作為索引使用的時(shí)候
__nonzero__(self) bool(self) 對象的布爾值
__getattr__(self, name) self.name # name 不存在 訪問一個(gè)不存在的屬性時(shí)
__setattr__(self, name, val) self.name = val 對一個(gè)屬性賦值時(shí)
__delattr__(self, name) del self.name 刪除一個(gè)屬性時(shí)
__getattribute(self, name) self.name 訪問任何屬性時(shí)
__getitem__(self, key) self[key] 使用索引訪問元素時(shí)
__setitem__(self, key, val) self[key] = val 對某個(gè)索引值賦值時(shí)
__delitem__(self, key) del self[key] 刪除某個(gè)索引值時(shí)
__iter__(self) for x in self 迭代時(shí)
__contains__(self, value) value in self, value not in self 使用 in 操作測試關(guān)系時(shí)
__concat__(self, value) self + other 連接兩個(gè)對象時(shí)
__call__(self [,...]) self(args) “調(diào)用”對象時(shí)
__enter__(self) with self as x: with 語句環(huán)境管理
__exit__(self, exc, val, trace) with self as x: with 語句環(huán)境管理
__getstate__(self) pickle.dump(pkl_file, self) 序列化
__setstate__(self) data = pickle.load(pkl_file) 序列化

希望這個(gè)表格對你對于什么時(shí)候應(yīng)該使用什么方法這個(gè)問題有所幫助。

    本站是提供個(gè)人知識管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    欧美一区日韩二区亚洲三区| 激情图日韩精品中文字幕| 亚洲精品欧美精品日韩精品| 欧美日韩国产亚洲三级理论片 | 亚洲国产精品无遮挡羞羞| 男人大臿蕉香蕉大视频 | 久久热在线免费视频精品| 久久精品久久精品中文字幕| 中文字幕日韩一区二区不卡| 日本办公室三级在线观看| 91亚洲精品国产一区| 久久综合九色综合欧美| 这里只有九九热精品视频| 亚洲男人天堂网在线视频| 九九久久精品久久久精品| 色一欲一性一乱—区二区三区| 国产女高清在线看免费观看| 人妻人妻人人妻人人澡| 国产毛片不卡视频在线| 中文字幕91在线观看| 日本高清加勒比免费在线| 国产午夜精品福利免费不| 亚洲一区二区亚洲日本| 久久综合九色综合欧美| 欧美加勒比一区二区三区| 国产不卡视频一区在线| 五月综合婷婷在线伊人| 中文字幕有码视频熟女| 精品人妻久久一品二品三品| 成人午夜视频在线播放| 不卡中文字幕在线视频| 国产亚洲二区精品美女久久| 男女午夜在线免费观看视频| 日韩1区二区三区麻豆| 欧美同性视频免费观看| 久久精品国产在热久久| 亚洲天堂精品1024| 91在线爽的少妇嗷嗷叫| 沐浴偷拍一区二区视频| 久久99亚洲小姐精品综合| 黑鬼糟蹋少妇资源在线观看|