全部博文(921)
分类: Python/Ruby
2015-07-31 16:42:28
首先说个我自己的笑话,话说Python算是我接触的稍微深点儿的第一门动态语言,用Python没多久就知道了有个Gevent,学习Gevent没多久就知道有个“猴子补丁”的概念。最开始觉得这么名字挺乐呵,猴子补丁,为啥叫这么个名儿?是因为猴子的动作迅速灵敏,Gevent也有这个特点,所以叫猴子补丁么?
然后这几天在看《松本行弘的程序世界》这本书,里面专门有一章讲了猴子补丁的设计,我就笑了,原来猴子补丁不是我理解的这个意思,更不是Gevent最开始这么做的。所谓的猴子补丁的含义是指在动态语言中,不去改变源码而对功能进行追加和变更。猴子补丁的这个叫法起源于Zope框架,大家在修正Zope的Bug的时候经常在程序后面追加更新部分,这些被称作是“杂牌军补丁(guerilla patch)”,后来guerilla就渐渐的写成了gorllia(猩猩),再后来就写了monkey(猴子),所以猴子补丁的叫法是这么莫名其妙的得来的。
猴子补丁这种东西充分利用了动态语言的灵活性,可以对现有的语言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函数的实现:
这个函数的功能就是从指定模块中查找旧的项,并把旧的项保存到saved字典中,然后将旧项替换成新项。
这里没有使用None,而是构建了一个空的object()作为默认属性,是NullPointer模式么?
然后是patch_module的实现:
patch_module的工作就是从gevent模块里面读取这两个属性,然后遍历调用patch_item进行替换。
可是有的时候我们不希望用补丁的东西,而是使用原先的模块去进行处理,该怎么办?前面提到过进行patch_item的时候会把旧的属性保存到名为saved的全局字典里面,如果要获得旧的模块属性,那么就要调用get_original函数从saved字典里面取出来。
猴子补丁的功能很强大,但是也带来了很多的风险,尤其是像gevent这种直接进行API替换的补丁,整个Python进程所使用的模块都会被替换,可能自己的代码能hold住,但是其它第三方库,有时候问题并不好排查,即使排查出来也是很棘手,所以,就像松本建议的那样,如果要使用猴子补丁,那么只是做功能追加,尽量避免大规模的API覆盖。