python mixin

Mix-in技術介紹
Mixin可 以譯爲混入,就是在不改變原對象的情況下對其進行擴展。本文介紹了在 Python 語言中,如何實現Mixin技術,及安裝的相應技巧。
什麼是Mix-in技術
Mix-in技術,中文不知道應該如何稱呼,但意思好象是混入。它 的作用是,在運行期間,動態改變類的基類或類的方法,從而使得類的表現可以發生變化。可以用在一個通用類接口中,根據不同的選擇使用不同的低層類實現,而 高層類不用發生變化。而且這一實現可以在運行過程中動態進行改變。由於我也是剛看到,大家有問題可以與我進行交流。這就是我看到的文 章的鏈接。
 
基類的增加
有一個類,
 
class foo:
    pass
我可以定義另外一個類,
 
class foobase:
    def hello(self):
        print "hello"
如果我直接調用:
 
>>> obj=foo()
>>> obj.hello()
這時你會看到出錯。那麼我可以這樣:
 
>>> foo.__bases__ +=(foobase,)
>>> obj.hello()
hello
成功了。原理是,每個類都有一個bases屬性,它是一個 tuple,用來存放所有的基類。而且在運行中,可以動態改變。所以當我們向其中增加新的基類時,再次調用原來不存在的函數,由於基類中的函數已經存在 了,所以這次成功了。
這是一個最簡單的應用,可以看到我們可以動態改變類的基類。 有幾個注意事項要說一下:
__bases__是一個tuple,所以增加一個值要使用 tuple類型,而單個元素tuple的寫法爲(foobase,)
類 必須先存在。所以,如果想使用這一技術,先要將相關的類的模塊導入(import)。
由於Mix-in是一種動態技術,以多繼承,對象爲基礎,而python正好是這樣的語言,使得在python中實現 這一技術非常容易。
 
函數替換
在前面,簡單地向大家介紹了一下Mix-in技術,它實現了基類的動態 增加。這樣我們就可以在運行時,根據選擇可以動態地增加基類,從而實現不同的目的。現在還有一個問題,就是,在基類與派生類中都有同名的函數,要如何處理 呢?
在Python中,如果派生類中有與基類同名的函數,那麼調用 函數時,會調用派生類的函數,而不是基類的函數,可以測試一下:
 
>>> class foobase:
        def a(self):
                print "hello"

>>> class foo(foobase):
        def a(self):
                print "foo"

>>> c=foo()
>>> c.a()
foo
可以看出,執行的是foo類的函數。這樣在使用Mix-in技術時,如果原來的類中存在與Mix類中同名的函數,那麼 Mix類中的函數不會運行,如果想對其進行替換怎麼辦呢?方法就是使用getattr()和setattr()函數。當然還是最簡單的。
定義兩個類:
 
>>> class foobase:
        def a(self):
                print "hello"

>>> class foo:
        def a(self):
                print "foo"

>>> f=getattr(foobase, "a")
>>> setattr(foo, "a", f.im_func) #f.im_func會得到真正的函數對象
>>> c=foo()
>>> c.a()
hello
可以看到,函數被替換了。
注意,使用dir(f)還會 看到其它的屬性im_class,它表示這個函數屬於哪個類,im_self表示屬於哪個實例。
 
Mix-in安裝函數
前面講了基本的實現技術,下面給大家介紹一個Mix-in安裝函數, 這個函數是從前面所說的文章copy下來的。
 
import types

def MixIn(pyClass, mixInClass, makeAncestor=0):
   if makeAncestor:
     if mixInClass not in pyClass.__bases__:
        pyClass.__bases__ = (mixInClass,) + pyClass.__bases__
   else:
     # Recursively traverse the mix-in ancestor
     # classes in order to support inheritance
     baseClasses = list(mixInClass.__bases__)
     baseClasses.reverse()
     for baseClass in baseClasses:
        MixIn(pyClass, baseClass)

     # Install the mix-in methods into the class
     for name in dir(mixInClass):
        if not name.startswith('__'):
        # skip private members
           member = getattr(mixInClass, name)
           if type(member) is types.MethodType:
               member = member.im_func
           setattr(pyClass, name, member)
這個函數可以將某個mix-in類安裝爲指定類的基類,同時可以通過關鍵字參數指定在基類中的順序,是最前還是最後。 因爲Python在處理基類時,是安順序進行的,所以安裝在最前則優先級最高。同時對於指定類的方法如果在mix-in類中存在,則將指定類中的方法替換 成mix-in類中的方法。
 
   if makeAncestor:
     if mixInClass not in pyClass.__bases__:
        pyClass.__bases__ = (mixInClass,) + pyClass.__bases__
如果makeAncestor爲1,表示是安裝在最前,則首先判斷在pyClass的基類中是否存在 mixInClass類,如果不存在,再進行安裝。
 
   else:
     # Recursively traverse the mix-in ancestor
     # classes in order to support inheritance
     baseClasses = list(mixInClass.__bases__)
     baseClasses.reverse()
     for baseClass in baseClasses:
        MixIn(pyClass, baseClass)
如果makeAncestor爲0,並不將mixInClass安裝在最後,原作者說他在實際中沒有這樣用的。那麼它 完成什麼任務呢?它實際完成了一個遞歸,即從mixInClass的最底層的基類開始(因爲mixInClass也可能是多重繼承而來的),對 pyClass中也存在的函數進行替換。這樣執行完畢後,mixInClass類中,包含所有基類中的函數,如果有與pyClass類中的函數重名的,都 將pyClass中的函數替換成mixInClass相應的函數。(有些複雜!)
 
     # Install the mix-in methods into the class
     for name in dir(mixInClass):
        if not name.startswith('__'):
        # skip private members
           member = getattr(mixInClass, name)
           if type(member) is types.MethodType:
               member = member.im_func
           setattr(pyClass, name, member)
這步完成重名函數的替換。首先去掉私有方法(私有方法名前有'__'). 得到mixInClass類中的指定名字的方法對象,判斷是否爲方法類型。因爲還有可能取到屬性。在types模塊中包含了一些類型,可以用它來判斷是否 爲方法類型。對於方法對象,如果是類方法,實際的函數應使用它的屬性im_func。然後將pyClass相應的方法替換成mixInClass中的方 法。
這樣就將mixInClass安裝爲pyClass的基類 了。
使用例子如:
 
from classa import classa
from classb import classb
MixIn(classa, classb) #將classb安裝爲classa的基類
發佈了33 篇原創文章 · 獲贊 25 · 訪問量 24萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章