详解Python编程中对Monkey Patch猴子补丁开发方式的运用


Posted in Python onMay 27, 2016

Monkey patch就是在运行时对已有的代码进行修改,达到hot patch的目的。Eventlet中大量使用了该技巧,以替换标准库中的组件,比如socket。首先来看一下最简单的monkey patch的实现。

class Foo(object):
  def bar(self):
    print 'Foo.bar'

def bar(self):
  print 'Modified bar'

Foo().bar()

Foo.bar = bar

Foo().bar()

由于Python中的名字空间是开放,通过dict来实现,所以很容易就可以达到patch的目的。

Python namespace

Python有几个namespace,分别是

  • locals
  • globals
  • builtin

其中定义在函数内声明的变量属于locals,而模块内定义的函数属于globals。

Python module Import & Name Lookup

当我们import一个module时,python会做以下几件事情

  • 导入一个module
  • 将module对象加入到sys.modules,后续对该module的导入将直接从该dict中获得
  • 将module对象加入到globals dict中

当我们引用一个模块时,将会从globals中查找。这里如果要替换掉一个标准模块,我们得做以下两件事情

将我们自己的module加入到sys.modules中,替换掉原有的模块。如果被替换模块还没加载,那么我们得先对其进行加载,否则第一次加载时,还会加载标准模块。(这里有一个import hook可以用,不过这需要我们自己实现该hook,可能也可以使用该方法hook module import)
如果被替换模块引用了其他模块,那么我们也需要进行替换,但是这里我们可以修改globals dict,将我们的module加入到globals以hook这些被引用的模块。
Eventlet Patcher Implementation

现在我们先来看一下eventlet中的Patcher的调用代码吧,这段代码对标准的ftplib做monkey patch,将eventlet的GreenSocket替换标准的socket。

from eventlet import patcher

# *NOTE: there might be some funny business with the "SOCKS" module
# if it even still exists
from eventlet.green import socket

patcher.inject('ftplib', globals(), ('socket', socket))

del patcher

inject函数会将eventlet的socket模块注入标准的ftplib中,globals dict被传入以做适当的修改。

让我们接着来看一下inject的实现。

__exclude = set(('__builtins__', '__file__', '__name__'))

def inject(module_name, new_globals, *additional_modules):
  """Base method for "injecting" greened modules into an imported module. It
  imports the module specified in *module_name*, arranging things so
  that the already-imported modules in *additional_modules* are used when
  *module_name* makes its imports.

  *new_globals* is either None or a globals dictionary that gets populated
  with the contents of the *module_name* module. This is useful when creating
  a "green" version of some other module.

  *additional_modules* should be a collection of two-element tuples, of the
  form (, ). If it's not specified, a default selection of
  name/module pairs is used, which should cover all use cases but may be
  slower because there are inevitably redundant or unnecessary imports.
  """
  if not additional_modules:
    # supply some defaults
    additional_modules = (
      _green_os_modules() +
      _green_select_modules() +
      _green_socket_modules() +
      _green_thread_modules() +
      _green_time_modules())

  ## Put the specified modules in sys.modules for the duration of the import
  saved = {}
  for name, mod in additional_modules:
    saved[name] = sys.modules.get(name, None)
    sys.modules[name] = mod

  ## Remove the old module from sys.modules and reimport it while
  ## the specified modules are in place
  old_module = sys.modules.pop(module_name, None)
  try:
    module = __import__(module_name, {}, {}, module_name.split('.')[:-1])

    if new_globals is not None:
      ## Update the given globals dictionary with everything from this new module
      for name in dir(module):
        if name not in __exclude:
          new_globals[name] = getattr(module, name)

    ## Keep a reference to the new module to prevent it from dying
    sys.modules['__patched_module_' + module_name] = module
  finally:
    ## Put the original module back
    if old_module is not None:
      sys.modules[module_name] = old_module
    elif module_name in sys.modules:
      del sys.modules[module_name]

    ## Put all the saved modules back
    for name, mod in additional_modules:
      if saved[name] is not None:
        sys.modules[name] = saved[name]
      else:
        del sys.modules[name]

  return module

注释比较清楚的解释了代码的意图。代码还是比较容易理解的。这里有一个函数__import__,这个函数提供一个模块名(字符串),来加载一个模块。而我们import或者reload时提供的名字是对象。

if new_globals is not None:
  ## Update the given globals dictionary with everything from this new module
  for name in dir(module):
    if name not in __exclude:
      new_globals[name] = getattr(module, name)

这段代码的作用是将标准的ftplib中的对象加入到eventlet的ftplib模块中。因为我们在eventlet.ftplib中调用了inject,传入了globals,而inject中我们手动__import__了这个module,只得到了一个模块对象,所以模块中的对象不会被加入到globals中,需要手动添加。
这里为什么不用from ftplib import *的缘故,应该是因为这样无法做到完全替换ftplib的目的。因为from … import *会根据__init__.py中的__all__列表来导入public symbol,而这样对于下划线开头的private symbol将不会导入,无法做到完全patch。

Python 相关文章推荐
Python selenium文件上传方法汇总
Nov 19 Python
python虚拟环境的安装配置图文教程
Oct 20 Python
Python实现识别手写数字 简易图片存储管理系统
Jan 29 Python
python中多层嵌套列表的拆分方法
Jul 02 Python
详解用python写网络爬虫-爬取新浪微博评论
May 10 Python
详解用python生成随机数的几种方法
Aug 04 Python
python序列类型种类详解
Feb 26 Python
keras实现VGG16 CIFAR10数据集方式
Jul 07 Python
详解Python中import机制
Sep 11 Python
python 通过 pybind11 使用Eigen加速代码的步骤
Dec 07 Python
python使用numpy中的size()函数实例用法详解
Jan 29 Python
Python中22个万用公式的小结
Jul 21 Python
Python程序中的观察者模式结构编写示例
May 27 #Python
Windows下python2.7.8安装图文教程
May 26 #Python
Java Web开发过程中登陆模块的验证码的实现方式总结
May 25 #Python
剖析Python的Twisted框架的核心特性
May 25 #Python
实例解析Python的Twisted框架中Deferred对象的用法
May 25 #Python
详解Python的Twisted框架中reactor事件管理器的用法
May 25 #Python
使用Python的Twisted框架编写非阻塞程序的代码示例
May 25 #Python
You might like
php使用CURL伪造IP和来源实例详解
2015/01/15 PHP
PHP获取一段文本显示点阵宽度和高度的方法
2015/03/12 PHP
php实现zip文件解压操作
2015/11/03 PHP
如何解决PHP使用mysql_query查询超大结果集超内存问题
2016/03/14 PHP
PHPStudy下如何为Apache安装SSL证书的方法步骤
2019/01/23 PHP
js中通过split函数分割字符串成数组小例子
2013/09/21 Javascript
jQuery遍历对象、数组、集合实例
2014/11/08 Javascript
JavaScript动态添加列的方法
2015/03/25 Javascript
微信小程序 教程之wxapp 视图容器 view
2016/10/19 Javascript
vue 登录滑动验证实现代码
2018/08/24 Javascript
vue-cli 3.x 配置Axios(proxyTable)跨域代理方法
2018/09/19 Javascript
浅谈angularJs函数的使用方法(大小写转换,拷贝,扩充对象)
2018/10/08 Javascript
node之本地服务器图片上传的方法示例
2019/03/26 Javascript
[03:01]2014DOTA2国际邀请赛 DC:我是核弹粉,为Burning和国土祝福
2014/07/13 DOTA
Python中类的初始化特殊方法
2017/12/01 Python
python抓取文件夹的所有文件
2018/02/27 Python
python批量修改文件编码格式的方法
2018/05/31 Python
numpy判断数值类型、过滤出数值型数据的方法
2018/06/09 Python
python实现本地图片转存并重命名的示例代码
2018/10/27 Python
python 申请内存空间,用于创建多维数组的实例
2019/12/02 Python
学python爬虫能做什么
2020/07/29 Python
使用Canvas操作像素的方法
2018/06/14 HTML / CSS
彪马西班牙官网:PUMA西班牙
2019/06/18 全球购物
信息工程学院毕业生推荐信
2013/11/05 职场文书
会计职业生涯规划书
2014/01/13 职场文书
纪念建党演讲稿范文
2014/01/13 职场文书
应届优秀本科大学毕业生自我鉴定
2014/01/21 职场文书
大学生在校学习的自我评价
2014/02/18 职场文书
学校消防演习方案
2014/02/19 职场文书
国际贸易专业个人鉴定
2014/02/22 职场文书
生物工程专业求职信
2014/09/03 职场文书
诉讼代理人授权委托书
2014/10/11 职场文书
事业单位岗位说明书
2015/10/08 职场文书
css3 实现文字闪烁效果的三种方式示例代码
2021/04/25 HTML / CSS
关于PostgreSQL JSONB的匹配和交集问题
2021/09/14 PostgreSQL
Spring Boot实现文件上传下载
2022/08/14 Java/Android