Python通向百萬程序員的秘籍!這些技巧你知道嗎?99%的不知道!
Python神奇方法是指一些允許在自定義類中增加“神奇”功能的方法。而在Python官方文檔中,有關這些方法的介紹或描述不僅內容分散,而且組織結構也相對松散。本文便對Python神奇方法做了系統的梳理。對於初學者亦或Python行家,都或多或少的會有些幫助。
Python神奇方法是指一些允許在自定義類中增加“神奇”功能的方法。而在Python官方文檔中,有關這些方法的介紹或描述不僅內容分散,而且組織結構也相對松散。本文便對Python神奇方法做了系統的梳理。對於初學者亦或Python行家,都或多或少的會有些幫助。
進群:548377875 即可獲取數十套PDF哦!
簡介
何為神奇方法呢?它們是面向Python中的一切,是一些特殊的方法允許在自己的定義類中定義增加“神奇”的功能。它們總是使用雙下劃線(比如__init__或__lt__),但它們的文檔沒有很好地把它們表現出來。所有這些神奇方法都出現在Python的官方文檔中,但內容相對分散,組織結構也顯得松散。還有你會難以發現一個實例(雖然他們被設計很棒,在語言參考中被詳細描述,可之後就會伴隨著枯燥的語法描述等)。
為了彌補Python官方文檔的這些缺陷,作者整理了這篇有關magic method的文章,旨在用作教程、復習或參考文檔。
如下,是是__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
在自定義類中運用操作符
神奇方法比較:
Python有一大堆magic method,旨在使用運算符實現對象之間的直觀比較,而非別扭的方法調用。它們還提供了一種方法來覆蓋用於對象比較的默認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)
定義了大於等於操作符,>=的行為。
舉一個例子,設想對單詞進行類定義。我們可能希望按照內部對字符串的默認比較行為,即字典序(通過字母)來比較單詞,也希望能夠基於某些其他的準則,像是長度或音節數。在本例中,我們通過單詞長度排序,以下給出實現:
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)
神奇方法數字:
就像你可以通過重載比較操作符的途徑來創建你自己的類實例,你同樣可以重載數字操作符。
一元操作符:
一元運算和函數僅有一個操作數,比如負數,絕對值等。
__pos__(self)
實現一元正數的行為(如:+some_object)
__neg__(self)
實現負數的行為(如: -some_object)
__abs__(self)
實現內建abs()函數的行為
__invert__(self)
實現用~操作符進行的取反行為。
常規算數操作符:
現在我們涵蓋了基本的二元運算符:+,-,*等等。其中大部分都是不言自明的。
__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也有各種各樣的神奇方法允許用戶自定義增量賦值行為。
這些方法都不會有返回值,因為賦值在Python中不會有任何返回值。反而它們只是改變類的狀態。列表如下:
__rxor__(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元組),且調整到具有相同的類型。
描述自定義類
用一個字符串來說明一個類這通常是有用的。在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的。
我們已經相當漂亮地幹完了神奇方法無聊的部分(無示例),至此我們已經討論了一些基礎的神奇方法,是時候讓我們向高級話題移動了。
控制屬性訪問
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賦值
# 在此自定義行為
以下是一個關於特殊屬性訪問方法的實際例子(註意,我們使用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)
制作自定義序列
很有多種方式可以讓你的類表現得像內建序列(字典,元組,列表,字符串等)。這些是我迄今為止最喜歡的神奇方法了,因為不合理的控制它們賦予了你一種魔術般地讓你的類實例整個全局函數數組漂亮工作的方式。
__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]
反射
你也可以通過定義神奇方法來控制如何反射使用內建函數isinstance()和issubclass()的行為。這些神奇方法是:
__instancecheck__(self, instance)
檢查一個實例是否是你定義類中的一個實例(比如,isinstance(instance, class))
__subclasscheck__(self, subclass)
檢查一個類是否是你定義類的子類(比如,issubclass(subclass, class))
可調用對象
這是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
#省略...
上下文管理器
上下文管理允許對對象進行設置和清理動作,用with聲明進行已經封裝的操作。上下文操作的行為取決於2個神奇方法:
__enter__(self)
定義塊用with聲明創建出來時上下文管理應該在塊開始做什麽。
__exit__(self, exception_type, exception_value, traceback)
定義在塊執行(或終止)之後上下文管理應該做什麽。
你也可以使用這些方法去創建封裝其他對象通用的上下文管理。看下面的例子:
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
構建描述符對象
描述符可以改變其他對象,也可以是訪問類中任一的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()
總結
這份指南的目標就是讓任何人都能讀懂它,不管讀者們是否具備Python或面向對象的編程經驗。如果你正準備學習Python,那你已經獲得了編寫功能豐富、優雅、易用的類的寶貴知識。如果你是一名中級Python程序員,你有可能已經拾起了一些概念、策略和一些好的方法來減少你編寫的代碼量。如果你是一名Python專家,你可能已經回顧了某些你可能已經遺忘的知識點,或者你又又有一些新的發現。不管你的經驗等級如何,希望你在這次Python神奇方法之旅中有所收獲!
Python通向百萬程序員的秘籍!這些技巧你知道嗎?99%的不知道!