什么是Monkey Patch猴子补丁
2017-09-14 22:17
393 查看
猴子补丁是一个概念,不是python中发明的,其他动态语言也有这么个概念。
《松本行弘的程序世界》这本书,里面专门有一章讲了猴子补丁的设计,所谓的猴子补丁的含义是指在动态语言中,不去改变源码而对功能进行追加和变更。猴子补丁的这个叫法起源于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函数的实现:
In [3]: 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的实现:
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覆盖。
《松本行弘的程序世界》这本书,里面专门有一章讲了猴子补丁的设计,所谓的猴子补丁的含义是指在动态语言中,不去改变源码而对功能进行追加和变更。猴子补丁的这个叫法起源于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函数的实现:
In [3]: 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的实现:
In [6]: 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覆盖。
相关文章推荐
- 什么是猴子补丁(monkey patch)
- 什么是猴子补丁(monkey patch)
- 什么是猴子补丁(monkey patch)
- 什么是猴子补丁(monkey patch)
- Pyhon基础:Monkey Patch(猴子补丁)
- Pyhon基础:Monkey Patch(猴子补丁)
- Pyhon基础:(猴子补丁)Monkey Patch
- Ruby使用Monkey Patch猴子补丁方式进行程序开发的示例
- Monkey Patch猴子补丁编程方式及其在Ruby中的运用
- 详解Python编程中对Monkey Patch猴子补丁开发方式的运用
- 详解Python编程中对Monkey Patch猴子补丁开发方式的运用
- 猴子排序 然而并没有什么用 233
- 做Rom其实没什么奥秘,浅显易懂的补丁制作教程,带刷机脚本示例
- [python]什么是monkey patch
- 猴子补丁
- 机器对外扫描,重装了系统也不行,打补丁也不行,什么原因?
- Python 猴子补丁
- Python Monkey patch猴子补丁
- 微信是什么?“猴子侦察兵”诠释微信是搜索!
- 基础入门_Python—Gevent异步/状态获取/超时设置/猴子补丁