相信很多朋友在程式設計的時候都會想修改一下已經寫好的程式行為程式碼,而最常見的方式就是通過子類來重寫父類的一些不滿足需求的方法。比如說下面這個例子。
1 2 3 4 5 6 7 |
class Dog: def bark(self): print 'Woof!' class Husky(Dog): def bark(self) print 'Howl!' |
我們可以用上述方式來修改我們自己寫的程式碼,但是我們應該怎麼修改第三方程式碼呢?當然,我們也可以自己編寫一個子類,呼叫子類的例項物件來實現修改,但是這樣可能會引入其他一系列問題。所以我們得想個辦法用我們自己的方法替換掉原來的物件方法,這就是本文接下來要介紹的“打補丁”的方式。
給類打補丁
如果我們想新增或是修改物件的方法的話,最簡單的方式莫過於給類打個補丁了。結合上面的例子,如果我們想給我們自己的 Dog
類寫一個新的 howl
方法的話,我們可以定義一個新的 howl
函式,像下面的程式碼一樣把它新增到我們的類中:
1 2 3 4 5 6 7 8 9 10 11 |
def newbark(self): print 'Wrooof!' def howl(self): print 'Howl!' # Replace an existing method Dog.bark = newbark # Add a new method Dog.howl = howl |
很簡單吧?但是這裡有幾個問題需要我們注意。首先,被修改的類的所有例項中的方法都會被更新,所以更新後的方法不僅僅存在於新建立的物件中,之前建立的所有物件都會擁有更新之後的方法,除非只是新增而不是覆蓋掉原來的方法。第二,你修改或者新增的方法應當是與物件繫結的,所以方法的第一個引數應當是被呼叫的物件(在這裡就是類的例項self
)。
給專案打補丁
單個物件也可以在不影響這個類的其他例項的情況下打補丁。但是還是有點小技巧的哦!先讓我們看看下面這個例子。
1 2 3 4 5 6 7 |
def herd(self, sheep): self.run() self.bark() self.run() border_collie = Dog() border_collie.herd = herd |
然後我們再試試呼叫新定義的方法:
1 2 3 4 5 6 7 8 |
border_collie.herd(sheep) TypeError: herd() takes exactly 2 arguments (1 given) The problem with the previous code is that the herd is not a bound method, just take a look at the following code: print border_collie.herd <function herd at 0xf9c5f0> |
出錯啦!引發錯誤的原因就是被呼叫的物件並沒有作為第一個引數傳給我們寫的函式。當然我們可以自己把引數傳進去,但是在這個替換類方法的場景下並不奏效。解決這個問題的正確方案是用 type
這個模組裡的 MethodType
函式,我們可以看看下面的示例程式碼:
1 2 3 4 5 6 7 8 9 |
import types border_collie = Dog() border_collie.herd = types.MethodType(herd, border_collie) print border_collie.herd <bound method ?.herd of <__main__.Dog instance at 0x23c9518>> border_collie.herd(sheep) |
現在我們的方法已經和例項繫結了,大功告成!
總結
執行中替換或者新增方法是非常有用的,比如說在單元測試中,有些負責和外界服務通訊的函式就需要替換掉,方便測試。這個技巧不僅很常用,而且在你最終決定要修改程式碼之前還可以保持程式碼的可維護性,是一個非常重要的技巧。