0 猴子補丁 - Monkey Patching
1 定義, 2 猴子補丁(monkey patching) 3 在運行時動態修改模塊、類或函數,通常是添加功能或修正缺陷。猴子補丁在代碼運行時 4 (內存中)發揮作用,不會修改源碼,因此只對當前運行的程序實例有效。 5 因為猴子補丁破壞了封裝,而且容易導致程序與補丁代碼的實現細節緊密耦合, 6 所以被視為臨時的變通方案,不是集成代碼的推薦方式。 7 8 monkey patching 9 Dynamically changing a module, class or function at run time, 10 usually to add features or fix bugs. Because it is done in memory 11 and not by changing the source code, a monkey patch only affects the 12 currently running instance of the program. Monkey patches break 13 encapsulation and tend to be tightly coupled to the implementation 14 details of the patched code units, so they are seen as temporary 15 work-arounds and not a recommended technique for code integration. 16 17 18 猴子補丁的名聲不太好。如果濫用,會導致系統難以理解和維護。補丁通常與目標緊密耦合, 19 因此很脆弱。另一個問題是,打了猴子補丁的兩個庫可能相互牽絆,因為第二個庫可能撤銷 20 了第一個庫的補丁。不過猴子補丁也有它的作用,例如可以在運行時讓類實現協議。 21 適配器設計模式通過實現全新的類解決這種問題。為 Python 打猴子補丁不難,但是有些局限。 22 與 Ruby 和 JavaScript 不同, Python 不允許為內置類型打猴子補丁。 23 其實這是優點,因為這樣可以確保 str 對象的方法始終是那些。 24 這一局限能減少外部庫打的補丁有沖突的概率。 25 26 看例子之前, 回顧一下兒 協議 protocol, 27 詳見 duck typing 一文, 28 http://www.cnblogs.com/zzyzz/p/7723272.html 29 在 Python 中創建功能完善的序列類型無需使用繼承, 只需實現符合序列協議的方法. 30 在面向對象編程中,協議是非正式的接口,只在文檔中定義,在代碼中不定義. 31 例如,Python 的序列協議只需要 __len__ 和 __getitem__ 兩個方法. 32 任對象/類型(A)只要使用標准的簽名和語義實現了這兩個方法,就能用在任何期待序列的地方, 33 然而A 是不是哪個類的子類無關緊要,只要提供了所需的方法即可.這就是 python 序列協議. 34 協議是非正式的,沒有強制力,因此如果你知道類的具體使用場景,通常只需要實現一個協議的部分. 35 例如,為了支持迭代,只需實現 __getitem__ 方法,沒必要提供 __len__方法. 36 37 例子, 38 注意 '>>>' 表示在交互式控制台 console 的輸入 39 >>>class ballgame(object): # 這個class 可以生成一個 ball 的組合集合. 40 ... colors = ['red','green','bule'] 41 ... boxs = ['1', '2', '3'] 42 43 ... def __init__(self): 44 ... self.ballsets = [(b, c) for b in self.boxs for c in self.colors] 45 46 ... def __getitem__(self, position): 47 ... return self.ballsets[position] 48 49 ... def __len__(self): 50 ... return len(self.ballsets) 51 52 # def __setitem__(self, key, value): 53 # self.ballsets[key] = value 54 ... def pickone(self,position): 55 ... return self.ballsets[position] 56 57 >>>ball = ballgame() 58 >>>ball.ballsets 59 [('1', 'red'), ('1', 'green'), ('1', 'bule'), ('2', 'red'), ('2', 'green'), 60 ('2', 'bule'), ('3', 'red'), ('3', 'green'), ('3', 'bule')] 61 >>>ball.pickone(2) 62 ('1', 'bule') # 這里會發現主要取得位置相同, 取出的 ball 組合就不變. 63 # 我們是通過 random 模塊重新排序 ball 示例中 ball 組合的順序. 64 >>>import random 65 >>>random.shuffle(ball) 66 Traceback (most recent call last): 67 File "<input>", line 1, in <module> 68 File "C:\Users\yzzhou8\AppData\Local\Programs\Python\Python36-32\lib\random.py", line 274, in shuffle 69 x[i], x[j] = x[j], x[i] 70 TypeError: 'ballgame' object does not support item assignment 71 72 #發現 random.shuffle 不可行,原因是 ballgame 類中沒有實現可變序列協議的 __setitem__ 方法, 73 #當然可以通過在類中實現 __setitem__ 特殊方法后,重新初始化實例解決這個問題, 74 #現在看看如果通過 monkey patching 解決這個問題. 75 76 >>>def setballset(ballgame,position,ballset): 77 ... ballgame.ballsets[position] = ballset 78 79 >>>ball.__setitem__ = setballset 80 >>>random.shuffle(ball) 81 >>>ball.ballsets 82 [('1', 'bule'), ('1', 'green'), ('2', 'bule'), ('3', 'green'), ('2', 'green'), 83 ('2', 'red'), ('1', 'red'), ('3', 'red'), ('3', 'bule')] 84 >>>random.shuffle(ball) 85 ball.ballsets 86 [('1', 'green'), ('3', 'red'), ('3', 'green'), ('1', 'red'), ('2', 'green'), 87 ('3', 'bule'), ('2', 'red'), ('2', 'bule'), ('1', 'bule')] 88 89 # 通過 monkey patching 在 ballgame 類的實例 ball 中 實現了 __setitem__ 特殊方法, 90 # 使 ball 實例變成一個符合可變序列協議的的實例,進而使我們可以通過 random.shuffle(ball) 91 # 來重新排序 ball.ballsets 92 93 >>>ball2 = ballgame() 94 >>>random.shuffle(ball2) 95 Traceback (most recent call last): 96 File "<input>", line 1, in <module> 97 File "C:\Users\yzzhou8\AppData\Local\Programs\Python\Python36-32\lib\random.py", line 274, in shuffle 98 x[i], x[j] = x[j], x[i] 99 TypeError: 'ballgame' object does not support item assignment 100 101 # Monkey patching 並沒有在原來類中起作用. 102 103 summarize, Monkey patching 之所以起作用, 要得益於 duck typing, 104 然而, 最終要歸功於 python 的 協議protocol 這一架構設計.