什么是猴子补丁
你好,很高興為你解答。
猴子補丁的由來
首先說個我自己的笑話,話說Python算是我接觸的稍微深點兒的第一門動態語言,用Python沒多久就知道了有個Gevent,學習Gevent沒多久就知道有個“猴子補丁”的概念。最開始覺得這么名字挺樂呵,猴子補丁,為啥叫這么個名兒?是因為猴子的動作迅速靈敏,Gevent也有這個特點,所以叫猴子補丁么?
然后這幾天在看《松本行弘的程序世界》這本書,里面專門有一章講了猴子補丁的設計,我就笑了,原來猴子補丁不是我理解的這個意思,更不是Gevent最開始這么做的。所謂的猴子補丁的含義是指在動態語言中,不去改變源碼而對功能進行追加和變更。猴子補丁的這個叫法起源于Zope框架,大家在修正Zope的Bug的時候經常在程序后面追加更新部分,這些被稱作是“雜牌軍補丁(guerilla patch)”,后來guerilla就漸漸的寫成了gorllia(猩猩),再后來就寫了monkey(猴子),所以猴子補丁的叫法是這么莫名其妙的得來的。
從Gevent學習猴子補丁的設計
猴子補丁這種東西充分利用了動態語言的靈活性,可以對現有的語言Api進行追加,替換,修改Bug,甚至性能優化等等。比如gevent的猴子補丁就可以對ssl、socket、os、time、select、thread、subprocess、sys等模塊的功能進行了增強和替換。我們來看下gevent中的猴子補丁模塊gevent.monkey的設計和實現,以后如果自己要設計實現猴子補丁,也可以按照這么個模式去做,我最近比較喜歡用ipython來閱讀python模塊的代碼,執行import gevent.monkey之后,只需要輸入??gevent.monkey就可以查看源碼了。
這個模塊核心的函數其實就這幾個,這些函數都位于模塊的上方,get_original、patch_item、remove_item、patch_module還有一個全局變量叫做saved,默認指向一個空的字典對象。
首先來看patch_item函數的實現:
def patch_item(module, attr, newitem):
NONE = object()
olditem = getattr(module, attr, NONE)
if olditem is not NONE:
saved.setdefault(module.__name__, {}).setdefault(attr, olditem)
setattr(module, attr, newitem)
這個函數的功能就是從指定模塊中查找舊的項,并把舊的項保存到saved字典中,然后將舊項替換成新項。
這里沒有使用None,而是構建了一個空的object()作為默認屬性,是NullPointer模式么?
然后是patch_module的實現:
def patch_module(name, items=None):
gevent_module = getattr(__import__('gevent.' + name), name)
module_name = getattr(gevent_module, '__target__', name)
module = __import__(module_name)
if items is None:
items = getattr(gevent_module, '__implements__', None)
if items is None:
raise AttributeError('%r does not have __implements__' % gevent_module)
for attr in items:
patch_item(module, attr, getattr(gevent_module, attr))
gevent有個約定,作為補丁的gevent模塊要包含這兩個屬性,__target__和__implements__,__target__是被補丁的默認模塊名稱,可以不指定,默認為gevent子模塊的名稱,比如gevent.socket是socket模塊的補丁,__implements__是要進行補丁的屬性,這是gevent.socket模塊中__implements__的定義:
# standard functions and classes that this module re-implements in a gevent-aware way:
__implements__ = ['create_connection',
'socket',
'SocketType',
'fromfd',
'socketpair']
patch_module的工作就是從gevent模塊里面讀取這兩個屬性,然后遍歷調用patch_item進行替換。
可是有的時候我們不希望用補丁的東西,而是使用原先的模塊去進行處理,該怎么辦?前面提到過進行patch_item的時候會把舊的屬性保存到名為saved的全局字典里面,如果要獲得舊的模塊屬性,那么就要調用get_original函數從saved字典里面取出來。
In [6]: sleep = gevent.monkey.get_original("time", "sleep")
In [7]: sleep
Out[7]: <function time.sleep>
In [8]: import time
In [9]: time.sleep
Out[9]: <function gevent.hub.sleep>
猴子補丁
猴子補丁的功能很強大,但是也帶來了很多的風險,尤其是像gevent這種直接進行API替換的補丁,整個Python進程所使用的模塊都會被替換,可能自己的代碼能hold住,但是其它第三方庫,有時候問題并不好排查,即使排查出來也是很棘手,所以,就像松本建議的那樣,如果要使用猴子補丁,那么只是做功能追加,盡量避免大規模的API覆蓋。
總結
- 上一篇: python特性(八):生成器对象的se
- 下一篇: 完全理解Python迭代对象、迭代器、生