Python是一門動態語言,Duck Typing概念遍佈其中,所以其中的Concept並不是以類型的約束爲載體,而是使用稱作爲協議的概念。那什麼是Duck Typing呢?
Duck Typing是鴨子類型,在動態語言中用的較多,是動態類型語言設計的一種風格。在這種風格中,一個對象有效的語義,不是由繼承自特定的類或實現特定的接口決定,而是由當前方法和屬性的集合決定。說白了就是並不關心對象是什麼類型,只關心行爲。
這個概念的名字來源於由James Whitcomb Riley提出的鴨子測試,“鴨子測試”可以這樣表述:
“當看到一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那麼這隻鳥就可以被稱爲鴨子。”
在鴨子類型中,關注的不是對象的類型本身,而是它是如何使用的。
看下面例子:
#coding=utf-8 class Duck: def quack(self): print u"嘎嘎嘎!" def feathers(self): print u"這是一隻Duck。" class Person: def quack(self): print u"hello,world!" def feathers(self): print u"這是人。" def in_the_forest(duck): duck.quack() duck.feathers() def game(): donald = Duck() john = Person() in_the_forest(donald) in_the_forest(john) game()
運行結果不用貼出來,都知道是什麼。那麼上面代碼從何體現出Duck Typing風格呢?
看看in_the_forest()函數,它對它的參數只有兩個要求,那就是這個參數必須要有quack()和feathers()兩個方法,不管它是什麼類型,只要有這兩個方法,都可以作爲in_the_forest()函數的參數,而類Duck和Person都實現了這個方法,所以它們的實例都可以作爲in_the_forest()的參數。這個就是鴨子類型,不關注對象類型本身,只關心行爲。這一點更C++還是有很大區別的,我也學習過C++,也瞭解C++多態性以及虛函數所以感覺這差別還是蠻大的。
現在來看看什麼是協議吧,簡單的說,在python中我需要調用你的某個方法,你正好有這個方法,這就是協議,比如在加法運算中,當出現加號(+)時,那麼按照數值類型相關的協議,python會自動去調用相應對象的__add__()方法,這就是協議。
python中有很多協議,比如下面代碼:
#coding=utf-8 a = 1 print dir(a)
結果如下:
['__abs__', '__add__', '__and__', '__class__', '__cmp__', '__coerce__', '__delattr__', '__div__', '__divmod__', '__doc__', '__float__', '__floordiv__', '__format__', '__getattribute__', '__getnewargs__', '__hash__', '__hex__', '__index__', '__init__', '__int__', '__invert__', '__long__', '__lshift__', '__mod__', '__mul__', '__neg__', '__new__', '__nonzero__', '__oct__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdiv__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'bit_length', 'conjugate', 'denominator', 'imag', 'numerator', 'real']
上面代碼中,a是整數對象,我們列出來該對象的所有屬性,上面的屬性列表中,以"__"開頭並且以"__"結尾的基本上都是對象協議依賴的方法。是不是很多呀,我們可以將它們分成以下幾類:
(1)類型轉換協議
類型轉換協議,顧名思義就是用來進行類型轉的咯,比如a=1,a是整型,想把它變成float浮點型,我們可以這樣:
#coding=utf-8 a = 1 print type(a) b = a.__float__() print b print type(b)
當然你也可以用內置函數float()。
除了__float__()外,還有__str__()、__repr__()、__int__()、__long__()、__nonzero__()等等,這些都是類型轉換協議依賴的方法。
在這裏提下__nonzero__(),他用t於將類轉換爲布爾值。通常在用類進行判斷和將類轉換成布爾值時調用。通常會在if等條件語句會調用該方法。比如:
#coding=utf-8 list1 = [] if list1: print "1"
上面代碼在執行過程中,先會調用list1對象的__nonzero__()方法,判讀它是不是空,是的話就會轉換爲False。假設有些對類沒有定義__nonzero__()方法,那麼將如何判斷真假呢?此時對象會獲取__len__()方法,如果該方法返回值爲0,則表示爲假。
如果一個類即沒有定義__nonzero__()方法,也沒有定義__len__()方法,那麼該類的所有實例用if判斷的話全爲真。
比如看下面例子:
#coding=utf-8 class A: def __nonzero__(self): print u"定義了__nonzero__()方法" return 1 class B: def __len__(self): print u"沒定義__nonzero__()方法,但定義了__len__()方法" return 1 class C: pass if A(): a = 1 print a if B(): a = 2 print a if C(): a = 3 print a
運行結果如下:
希望上面的例子能夠幫助你理解部分知識。
(2)用以比較大小的協議
這個協議依賴於__cmp__()方法,當兩者相等時返回0,self<other時返回負值,反之返回正值。但是這種返回有點複雜,Python又定義了__eq__()、__ne__()、__lt__()、__gt__()等方法來實現相等、不等、小於和大於的判定。這也是Python對==,!=,<,>等操作符進行重載支撐的機制,也就是說重載這些操作符,就是要重新定義對應的方法。
(3)與數值類型相關的協議
這一類方法比較多,主要是數值之間的一些操作。
分類 | 方法 | 操作符/函數 | 說明 |
數值運算符 | __add__ | + | 加 |
__sub__ | - | 減 | |
__mul__ | * | 乘 | |
__div__ | / | 除 | |
__floordiv__ | // | 整除 | |
__truediv__ | / | 真除法 | |
__pow__ | ** | 冪運算 | |
__mod__ | % | 取餘 | |
__divmod__ | divmod() | 餘、除 | |
位運算符 | __lshift__ | << | 向左移位 |
__rshift__ | >> | 向右移位 | |
__and__ | & | 與 | |
__or__ | | | 或 | |
__xor__ | ^ | 異或 | |
__invert__ | ~ | 非 | |
運算賦值符 | __iadd__ | += | |
__isub__ | -= | ||
__imul__ | *= | ||
__idiv__ | /= | ||
__ifloordiv__ | //= | ||
__itruediv__ | /= | ||
__ipow__ | **= | ||
__imod__ | %= | ||
__ilshift__ | <<= | ||
__irshift__ | >>= | ||
__iand__ | &= | ||
__ior__ | |= | ||
__ixor__ | ^= | ||
其它 | __pos__ | + | 正 |
__neg__ | - | 負 | |
__abs__ | abs() | 絕對值 |
舉個例子,比如C++中,"<<"可表示輸出流,我們也可以將python中"<<"重載爲具有輸出流功能。代碼如下:
#coding=utf-8 class endl(object): pass class Cout(object): def __lshift__(self, other): if other is endl: print return print other return self cout = Cout() cout << "hello" << "world" << endl
運行結果如下:
不過在python中,將"<<"操作符重載爲具有輸出流功能,基本上是沒有意義,在這裏只是舉例說明。
另外Python中還有一個特有的概念:反運算。例如a + b,調用的是a的__add__()方法,如果a沒有__add__()方法,但b有,可是這種寫法調用b.__add__()又不對的,這時候Python有了一個反運算協議,它會去檢測b中有沒有__radd__(),如果有的話,那麼a爲參數調用之。類似__radd__()方法,所有數值運算和位運算都之處,規則一律在前面加前綴r。
(4)容器類協議
既然爲容器,那麼肯定會有查詢、取值、刪除、賦值、求長度等等一系列動作行爲,那麼必有對應的方法與這些操作對應。打印出dir(list)的值,結果如下:
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__delslice__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__setslice__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
其中:
求長度,也就是查詢容器內有多少個對象,用__len__()方法,內置函數len()就是通過該方法實現的。
取值、刪除、賦值依次對應__getitem__()、__delitem__()、__setitem__(),有些容器沒有__setitem__(),比如字符串,應爲字符串是不可變對象
__iter__()實現了迭代協議,__reversed__()則提供了對內置函數reversed()的支持,用來排序。
還有成員關係判定符in和not in,對應的方法是__contains__()
(5)可調用對象協議
可調用對象,也就是類似函數對象,能夠讓類實例表現的像函數一樣,這樣可以讓每一個函數調用都有所不同。怎麼理解這句話呢?還是看例子吧。
#coding=utf-8 class A(object): def __init__(self,name): self.name = name def __call__(self): print "dongn something with %s"%(self.name) a = A('li lei') b = A('han meimei') print a() print b()
運行結果如下:
看看上面的例子,我們能夠像調用函數一樣調用實例
對象通過提供__call__(slef, [,*args [,**kwargs]])方法可以模擬函數的行爲,如果一個對象x提供了該方法,就可以像函數一樣使用它,也就是說x(arg1, arg2...) 等同於調用x.__call__(self, arg1, arg2) 。
(6)哈希協議
如果對象有__hash__()方法,表示是一個可哈希對象。__hash__()方法支持這hash()這個內置函數。按照文檔裏面的解釋“如果一個對象是可哈希的,那麼在它的生存期內必須不可變(需要一個哈希函數),而且可以和其他對象比較(需要比較方法).比較值相同的對象一定有相同的哈希值”。
這也就是說所有不可變的內置類型t都是可哈希的,比如string,tuple。所有可變的內置類型都是不可哈希的,比如list,dict(即沒有__hash__()方法)。字典的key必須是可哈希的,所以tuple,string可以做key,而list不能做key。
(7)上下文管理器協議
這個在之前的博文中講過,在此不做過多解釋,可參考之前的相關博文。http://11026142.blog.51cto.com/11016142/1845862