Chinaunix首页 | 论坛 | 博客
  • 博客访问: 5118978
  • 博文数量: 921
  • 博客积分: 16037
  • 博客等级: 上将
  • 技术积分: 8469
  • 用 户 组: 普通用户
  • 注册时间: 2006-04-05 02:08
文章分类

全部博文(921)

文章存档

2020年(1)

2019年(3)

2018年(3)

2017年(6)

2016年(47)

2015年(72)

2014年(25)

2013年(72)

2012年(125)

2011年(182)

2010年(42)

2009年(14)

2008年(85)

2007年(89)

2006年(155)

分类: Python/Ruby

2015-07-31 16:42:28

猴子补丁的由来

首先说个我自己的笑话,话说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函数的实现:

  1. def patch_item(module, attr, newitem):
  2.     NONE = object()
  3.     olditem = getattr(module, attr, NONE)
  4.     if olditem is not NONE:
  5.         saved.setdefault(module.__name__, {}).setdefault(attr, olditem)
  6.     setattr(module, attr, newitem)

这个函数的功能就是从指定模块中查找旧的项,并把旧的项保存到saved字典中,然后将旧项替换成新项。

这里没有使用None,而是构建了一个空的object()作为默认属性,是NullPointer模式么?

然后是patch_module的实现:


  1. def patch_module(name, items=None):
  2.     gevent_module = getattr(__import__('gevent.' + name), name)
  3.     module_name = getattr(gevent_module, '__target__', name)
  4.     module = __import__(module_name)
  5.     if items is None:
  6.         items = getattr(gevent_module, '__implements__', None)
  7.         if items is None:
  8.             raise AttributeError('%r does not have __implements__' % gevent_module)
  9.     for attr in items:
  10.         patch_item(module, attr, getattr(gevent_module, attr))

gevent有个约定,作为补丁的gevent模块要包含这两个属性,__target__和__implements__,__target__是被补丁的默认模块名称,可以不指定,默认为gevent子模块的名称,比如gevent.socket是socket模块的补丁,__implements__是要进行补丁的属性,这是gevent.socket模块中__implements__的定义:

  1. # standard functions and classes that this module re-implements in a gevent-aware way:
  2. __implements__ = ['create_connection',
  3.                   'socket',
  4.                   'SocketType',
  5.                   'fromfd',
  6.                   'socketpair']


patch_module的工作就是从gevent模块里面读取这两个属性,然后遍历调用patch_item进行替换。

可是有的时候我们不希望用补丁的东西,而是使用原先的模块去进行处理,该怎么办?前面提到过进行patch_item的时候会把旧的属性保存到名为saved的全局字典里面,如果要获得旧的模块属性,那么就要调用get_original函数从saved字典里面取出来。


  1. In [6]: sleep = gevent.monkey.get_original("time", "sleep")
  2. In [7]: sleep
  3. Out[7]: <function time.sleep>
  4. In [8]: import time
  5. In [9]: time.sleep
  6. Out[9]: <function gevent.hub.sleep>

猴子补丁

猴子补丁的功能很强大,但是也带来了很多的风险,尤其是像gevent这种直接进行API替换的补丁,整个Python进程所使用的模块都会被替换,可能自己的代码能hold住,但是其它第三方库,有时候问题并不好排查,即使排查出来也是很棘手,所以,就像松本建议的那样,如果要使用猴子补丁,那么只是做功能追加,尽量避免大规模的API覆盖。


原文地址

阅读(1797) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~