Python的對象協議

    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)

wKioL1fzqnzwH3kxAAAb5xZ86lE377.png-wh_50

當然你也可以用內置函數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

運行結果如下:

wKiom1fzsLezUZRqAAAmngDjIDs276.png-wh_50

希望上面的例子能夠幫助你理解部分知識。


(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

運行結果如下:

wKioL1f0stDBrUuSAAAaZUbhgYw364.png-wh_50

不過在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()

運行結果如下:

wKioL1fzxLvz9_mYAAAehdrZUyE354.png-wh_50

看看上面的例子,我們能夠像調用函數一樣調用實例

對象通過提供__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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章