介紹
在Python中,所有以“__”雙下劃線包起來的方法,都統(tǒng)稱為“Magic Method”,例如類的初始化方法 __init__,Python中所有的魔術(shù)方法均在官方文檔中有相應(yīng)描述,但是對于官方的描述比較混亂而且組織比較松散。很難找到有一個例子。
構(gòu)造和初始化
每個Pythoner都知道一個最基本的魔術(shù)方法, __init__ 。通過此方法我們可以定義一個對象的初始操作。然而,當(dāng)調(diào)用 x = SomeClass() 的時候, __init__ 并不是第一個被調(diào)用的方法。實際上,還有一個叫做__new__ 的方法,兩個共同構(gòu)成了“構(gòu)造函數(shù)”。
__new__是用來創(chuàng)建類并返回這個類的實例, 而__init__只是將傳入的參數(shù)來初始化該實例。
在對象生命周期調(diào)用結(jié)束時,__del__ 方法會被調(diào)用,可以將__del__理解為“構(gòu)析函數(shù)”。下面通過代碼的看一看這三個方法:
from os.path import joinclass FileObject: '''給文件對象進(jìn)行包裝從而確認(rèn)在刪除時文件流關(guān)閉''' def __init__(self, filepath='~', filename='sample.txt'): #讀寫模式打開一個文件 self.file = open(join(filepath, filename), 'r+') def __del__(self): self.file.close() del self.file
控制屬性訪問
許多從其他語言轉(zhuǎn)到Python的人會抱怨它缺乏類的真正封裝。(沒有辦法定義私有變量,然后定義公共的getter和setter)。Python其實可以通過魔術(shù)方法來完成封裝。我們來看一下:
__getattr__(self, name)
定義當(dāng)用戶試圖獲取一個不存在的屬性時的行為。這適用于對普通拼寫錯誤的獲取和重定向,對獲取一些不建議的屬性時候給出警告(如果你愿意你也可以計算并且給出一個值)或者處理一個 AttributeError 。只有當(dāng)調(diào)用不存在的屬性的時候會被返回。
__setattr__(self, name, value)
與__getattr__(self, name)不同,__setattr__ 是一個封裝的解決方案。無論屬性是否存在,它都允許你定義對對屬性的賦值行為,以為這你可以對屬性的值進(jìn)行個性定制。實現(xiàn)__setattr__時要避免”無限遞歸”的錯誤。
__delattr__
與 __setattr__ 相同,但是功能是刪除一個屬性而不是設(shè)置他們。實現(xiàn)時也要防止無限遞歸現(xiàn)象發(fā)生。
__getattribute__(self, name)
__getattribute__定義了你的屬性被訪問時的行為,相比較,__getattr__只有該屬性不存在時才會起作用。因此,在支持__getattribute__的Python版本,調(diào)用__getattr__前必定會調(diào)用 __getattribute__。__getattribute__同樣要避免”無限遞歸”的錯誤。需要提醒的是,最好不要嘗試去實現(xiàn)__getattribute__,因為很少見到這種做法,而且很容易出bug。
在進(jìn)行屬性訪問控制定義的時候很可能會很容易引起“無限遞歸”。如下面代碼:
# 錯誤用法def __setattr__(self, name, value): self.name = value # 每當(dāng)屬性被賦值的時候(如self.name = value), ``__setattr__()`` 會被調(diào)用,這樣就造成了遞歸調(diào)用。 # 這意味這會調(diào)用 ``self.__setattr__('name', value)`` ,每次方法會調(diào)用自己。這樣會造成程序崩潰。# 正確用法def __setattr__(self, name, value): self.__dict__[name] = value # 給類中的屬性名分配值 # 定制特有屬性
Python的魔術(shù)方法很強(qiáng)大,但是用時卻需要慎之又慎,了解正確的使用方法非常重要。
創(chuàng)建自定義容器
有很多方法可以讓你的Python類行為向內(nèi)置容器類型一樣,比如我們常用的list、dict、tuple、string等等。Python的容器類型分為可變類型(如list、dict)和不可變類型(如string、tuple),可變?nèi)萜骱筒豢勺內(nèi)萜鞯膮^(qū)別在于,不可變?nèi)萜饕坏┵x值后,不可對其中的某個元素進(jìn)行修改。 在講創(chuàng)建自定義容器之前,應(yīng)該先了解下協(xié)議。這里的協(xié)議跟其他語言中所謂的”接口”概念很像,它給你很多你必須定義的方法。然而在Python中的協(xié)議是很不正式的,不需要明確聲明實現(xiàn)。事實上,他們更像一種指南。
自定義容器的magic method
下面細(xì)致了解下定義容器可能用到的魔術(shù)方法。首先,實現(xiàn)不可變?nèi)萜鞯脑挘阒荒芏x __len__ 和 __getitem__ (下面會講更多)。可變?nèi)萜鲄f(xié)議則需要所有不可變?nèi)萜鞯乃校硗膺€需要 __setitem__ 和 __delitem__。如果你希望你的對象是可迭代的話,你需要定義 __iter__ 會返回一個迭代器。迭代器必須遵循迭代器協(xié)議,需要有 __iter__(返回它本身) 和 next。
__len__(self)
返回容器的長度。對于可變和不可變?nèi)萜鞯膮f(xié)議,這都是其中的一部分。
__getitem__(self, key)
定義當(dāng)某一項被訪問時,使用self[key]所產(chǎn)生的行為。這也是不可變?nèi)萜骱涂勺內(nèi)萜鲄f(xié)議的一部分。如果鍵的類型錯誤將產(chǎn)生TypeError;如果key沒有合適的值則產(chǎn)生KeyError。
__setitem__(self, key, value)
當(dāng)你執(zhí)行self[key] = value時,調(diào)用的是該方法。
__delitem__(self, key)
定義當(dāng)一個項目被刪除時的行為(比如 del self[key])。這只是可變?nèi)萜鲄f(xié)議中的一部分。當(dāng)使用一個無效的鍵時應(yīng)該拋出適當(dāng)?shù)漠惓!?/p>
__iter__(self)
返回一個容器迭代器,很多情況下會返回迭代器,尤其是當(dāng)內(nèi)置的iter()方法被調(diào)用的時候,以及當(dāng)使用for x in container:方式循環(huán)的時候。迭代器是它們本身的對象,它們必須定義返回self的__iter__方法。
__reversed__(self)
實現(xiàn)當(dāng)reversed()被調(diào)用時的行為。應(yīng)該返回序列反轉(zhuǎn)后的版本。僅當(dāng)序列可以是有序的時候?qū)崿F(xiàn)它,例如對于列表或者元組。
__contains__(self, item)
定義了調(diào)用in和not in來測試成員是否存在的時候所產(chǎn)生的行為。你可能會問為什么這個不是序列協(xié)議的一部分?因為當(dāng)__contains__沒有被定義的時候,如果沒有定義,那么Python會迭代容器中的元素來一個一個比較,從而決定返回True或者False。
__missing__(self, key)
dict字典類型會有該方法,它定義了key如果在容器中找不到時觸發(fā)的行為。比如d = {‘a(chǎn)’: 1}, 當(dāng)你執(zhí)行d[notexist]時,d.__missing__[‘notexist’]就會被調(diào)用。
一個例子
下面是書中的例子,用魔術(shù)方法來實現(xiàn)Haskell語言中的一個數(shù)據(jù)結(jié)構(gòu)。
# -*- coding: utf-8 -*-class FunctionalList: ''' 實現(xiàn)了內(nèi)置類型list的功能,并豐富了一些其他方法: 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): 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 FunctionalList(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個 return self.values[n:] def take(self, n): # 獲取前N個元素 return self.values[:n]
其實在collections模塊中已經(jīng)有了很多類似的實現(xiàn),比如Counter、OrderedDict等等。
反射
你也可以控制怎么使用內(nèi)置在函數(shù)sisinstance()和issubclass()方法 反射定義魔術(shù)方法. 這個魔術(shù)方法是:
__instancecheck__(self, instance)
檢查一個實例是不是你定義的類的實例
__subclasscheck__(self, subclass)
檢查一個類是不是你定義的類的子類
這些魔術(shù)方法的用例看起來很小, 并且確實非常實用. 它們反應(yīng)了關(guān)于面向?qū)ο蟪绦蛏弦恍┲匾臇|西在Python上,并且總的來說Python: 總是一個簡單的方法去找某些事情, 即使是沒有必要的. 這些魔法方法可能看起來不是很有用, 但是一旦你需要它們,你會感到慶幸它們的存在。
可調(diào)用的對象
你也許已經(jīng)知道,在Python中,方法是最高級的對象。這意味著他們也可以被傳遞到方法中,就像其他對象一樣。這是一個非常驚人的特性。
在Python中,一個特殊的魔術(shù)方法可以讓類的實例的行為表現(xiàn)的像函數(shù)一樣,你可以調(diào)用它們,將一個函數(shù)當(dāng)做一個參數(shù)傳到另外一個函數(shù)中等等。這是一個非常強(qiáng)大的特性,其讓Python編程更加舒適甜美。
__call__(self, [args...])
允許一個類的實例像函數(shù)一樣被調(diào)用。實質(zhì)上說,這意味著 x() 與 x.__call__() 是相同的。注意 __call__ 的參數(shù)可變。這意味著你可以定義 __call__ 為其他你想要的函數(shù),無論有多少個參數(shù)。
__call__ 在那些類的實例經(jīng)常改變狀態(tài)的時候會非常有效。調(diào)用這個實例是一種改變這個對象狀態(tài)的直接和優(yōu)雅的做法。用一個實例來表達(dá)最好不過了:
# -*- coding: UTF-8 -*-class Entity: """ 調(diào)用實體來改變實體的位置 """def __init__(self, size, x, y): self.x, self.y = x, y self.size = sizedef __call__(self, x, y): """ 改變實體的位置 """ self.x, self.y = x, y
上下文管理
with聲明是從Python2.5開始引進(jìn)的關(guān)鍵詞。你應(yīng)該遇過這樣子的代碼:
with open('foo.txt') as bar: # do something with bar
在with聲明的代碼段中,我們可以做一些對象的開始操作和退出操作,還能對異常進(jìn)行處理。這需要實現(xiàn)兩個魔術(shù)方法: __enter__ 和 __exit__。
__enter__(self)
定義了當(dāng)使用with語句的時候,會話管理器在塊被初始創(chuàng)建時要產(chǎn)生的行為。請注意,__enter__的返回值與with語句的目標(biāo)或者as后的名字綁定。
__exit__(self, exception_type, exception_value, traceback)
定義了當(dāng)一個代碼塊被執(zhí)行或者終止后,會話管理器應(yīng)該做什么。它可以被用來處理異常、執(zhí)行清理工作或做一些代碼塊執(zhí)行完畢之后的日常工作。如果代碼塊執(zhí)行成功,exceptiontype,exceptionvalue,和traceback將會為None。否則,你可以選擇處理這個異常或者是直接交給用戶處理。如果你想處理這個異常的話,請確保__exit在所有語句結(jié)束之后返回True。如果你想讓異常被會話管理器處理的話,那么就讓其產(chǎn)生該異常。
創(chuàng)建對象描述器
描述器是通過獲取、設(shè)置以及刪除的時候被訪問的類。當(dāng)然也可以改變其它的對象。描述器并不是獨(dú)立的。相反,它意味著被一個所有者類持有。當(dāng)創(chuàng)建面向?qū)ο蟮臄?shù)據(jù)庫或者類,里面含有相互依賴的屬相時,描述器將會非常有用。一種典型的使用方法是用不同的單位表示同一個數(shù)值,或者表示某個數(shù)據(jù)的附加屬性。
為了成為一個描述器,一個類必須至少有__get__,__set__,__delete__方法被實現(xiàn):
__get__(self, instance, owner)
定義了當(dāng)描述器的值被取得的時候的行為。instance是擁有該描述器對象的一個實例。owner是擁有者本身
__set__(self, instance, value)
定義了當(dāng)描述器的值被改變的時候的行為。instance是擁有該描述器類的一個實例。value是要設(shè)置的值。
__delete__(self, instance)
定義了當(dāng)描述器的值被刪除的時候的行為。instance是擁有該描述器對象的一個實例。
下面是一個描述器的實例:單位轉(zhuǎn)換。
# -*- coding: UTF-8 -*-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.2808class Distance(object): """ 用米和英寸來表示兩個描述器之間的距離 """ meter = Meter(10) foot = Foot()
使用時:
>>>d = Distance()>>>print d.foot>>>print d.meter32.80810.0
復(fù)制
有時候,尤其是當(dāng)你在處理可變對象時,你可能想要復(fù)制一個對象,然后對其做出一些改變而不希望影響原來的對象。這就是Python的copy所發(fā)揮作用的地方。
__copy__(self)
定義了當(dāng)對你的類的實例調(diào)用copy.copy()時所產(chǎn)生的行為。copy.copy()返回了你的對象的一個淺拷貝——這意味著,當(dāng)實例本身是一個新實例時,它的所有數(shù)據(jù)都被引用了——例如,當(dāng)一個對象本身被復(fù)制了,它的數(shù)據(jù)仍然是被引用的(因此,對于淺拷貝中數(shù)據(jù)的更改仍然可能導(dǎo)致數(shù)據(jù)在原始對象的中的改變)。
__deepcopy__(self, memodict={})
定義了當(dāng)對你的類的實例調(diào)用copy.deepcopy()時所產(chǎn)生的行為。copy.deepcopy()返回了你的對象的一個深拷貝——對象和其數(shù)據(jù)都被拷貝了。memodict是對之前被拷貝的對象的一個緩存——這優(yōu)化了拷貝過程并且阻止了對遞歸數(shù)據(jù)結(jié)構(gòu)拷貝時的無限遞歸。當(dāng)你想要進(jìn)行對一個單獨(dú)的屬性進(jìn)行深拷貝時,調(diào)用copy.deepcopy(),并以memodict為第一個參數(shù)。
附錄
用于比較的魔術(shù)方法
數(shù)值計算的魔術(shù)方法
單目運(yùn)算符和函數(shù)
雙目運(yùn)算符或函數(shù)
增量運(yùn)算
類型轉(zhuǎn)換
-
函數(shù)
+關(guān)注
關(guān)注
3文章
4307瀏覽量
62433 -
python
+關(guān)注
關(guān)注
56文章
4782瀏覽量
84460
原文標(biāo)題:Python 開發(fā)者不得不知的魔術(shù)方法(Magic Method)
文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運(yùn)維】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論