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

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。

(0)

相关推荐

  • 用Greasemonkey 脚本收藏网站会员信息到本地

    一.脚本功能介绍 正常情况下,如果你在会员搜索结果页通过相片看好某个会员(所谓眼缘好的会员),想快速记录下这个会员的信息并不是一件容易的事情,你也许会在会员相片上单击右键,然后把这个会员的主页地址先记下来,一个页面如果有较多看好的会员想收藏的话,你还得重复上面的操作.默认搜索结果页显示效果如下图: 安装我写的Greasemonkey脚本后,搜索结果页就会发生一点改变,"给我写信"按钮会变成"收藏"复选框,效果如下图,注意红框标识与前面图片的变化对比: 现在假设你想收

  • Android自动测试工具Monkey

    前言: 最近开始研究Android自动化测试方法,对其中的一些工具.方法和框架做了一些简单的整理,其中包括android测试框架.CTS.Monkey.Monkeyrunner.benchmark.其它test tool等等.因接触时间很短,很多地方有不足之处,希望能和大家多多交流. 一.Monkey定义 探索软件测试工具有哪些,本文主要介绍Monkey工具.Monkey测试是Android平台自动化测试的一种手段,通过Monkey程序模拟用户触摸屏幕.滑动.按键等操作来对设备上的程序进行压力测

  • Android自动测试工具Monkey的实现方法

    1. Android Monkey 实现操作流程: 准备:在eclipse里安装Phyon插件,可以选择在线安装,也可以下载zip解压后放在eclipse安装目录的dropins下,如 : /personal/software/android_developtools/adt-bundle-mac-x86_64-20130522/eclipse/dropins/PyDev 2.8.2 插件准备就绪就重启eclipse,检验PyDev是否正常工作,然后开始MonkeyRunner测试: 第一步:

  • android压力测试命令monkey详解

    一.Monkey 是什么?Monkey 就是SDK中附带的一个工具. 二.Monkey 测试的目的?:该工具用于进行压力测试. 然后开发人员结合monkey 打印的日志 和系统打印的日志,结局测试中出现的问题. 三.Monkey 测试的特点?Monkey 测试,所有的事件都是随机产生的,不带任何人的主观性. 四.Monkey 命令详解 1).标准的monkey 命令[adb shell] monkey [options] <eventcount> , 例如:adb shell monkey -

  • Ruby使用Monkey Patch猴子补丁方式进行程序开发的示例

    猴子补丁(Monkey Patch)是一种特殊的编程技巧.Monkey patch 可以用来在运行时动态地修改(扩展)类或模块.我们可以通过添加 Monkey Patch 来修改不满足自己需求的第三方库,也可以添加 Monkey Patch 零时修改代码中的错误. 词源 Monkey patch 最早被称作 Guerrilla patch,形容这种补丁像游击队员一样狡猾.后来因为发音相似,被称为 Gorilla patch.因为大猩猩不够可爱,后改称为 Monkey patch. 使用场景 以我

  • javascript SpiderMonkey中的函数序列化如何进行

    在Javascript中,函数可以很容易的被序列化(字符串化),也就是得到函数的源码.但其实这个操作的内部实现(引擎实现)并不是你想象的那么简单.SpiderMonkey中一共使用过两种函数序列化的技术:一种是利用反编译器(decompiler)将函数编译后的字节码反编译成源码字符串,另一种是在将函数编译成字节码之前就把函数源码压缩并存储下来,用到的时候再解压还原. 如何进行函数序列化 在SpiderMonkey中,能将函数序列化的方法或函数有三个:Function.prototype.toSt

  • android monkey自动化测试改为java调用monkeyrunner Api

    众所周知,一般情况下我们使用android中的monkeyrunner进行自动化测试时,使用的是python语言来写测试脚本.不过,最近发现可以用java调用monkeyrunner Api,用java语言写测试脚本. 于是,就简单研究了一下.这里做一些总结.希望有对在研究的午饭可以有所用处. 开始时,搜素到一些零碎的教程,说使用java调用monkeyrunner时,需要导入android sdk  tools路径下的lib里面的4个包:ddmlib.jar,guavalib.jar,monk

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

    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中的名字空间是开放

  • 详解Python编程中基本的数学计算使用

    数 在 Python 中,对数的规定比较简单,基本在小学数学水平即可理解. 那么,做为零基础学习这,也就从计算小学数学题目开始吧.因为从这里开始,数学的基础知识列位肯定过关了. >>> 3 3 >>> 3333333333333333333333333333333333333333 3333333333333333333333333333333333333333L >>> 3.222222 3.222222 上面显示的是在交互模式下,如果输入 3,就显

  • 详解Python编程中time模块的使用

    一.简介 time模块提供各种操作时间的函数 说明:一般有两种表示时间的方式: 第一种是时间戳的方式(相对于1970.1.1 00:00:00以秒计算的偏移量),时间戳是惟一的 第二种以数组的形式表示即(struct_time),共有九个元素,分别表示,同一个时间戳的struct_time会因为时区不同而不同 year (four digits, e.g. 1998) month (1-12) day (1-31) hours (0-23) minutes (0-59) seconds (0-5

  • 详解Python编程中包的概念与管理

    Python中的包 包是一个分层次的文件目录结构,它定义了一个由模块及子包,和子包下的子包等组成的Python的应用环境. 考虑一个在Phone目录下的pots.py文件.这个文件有如下源代码: #!/usr/bin/python # -*- coding: UTF-8 -*- def Pots(): print "I'm Pots Phone" 同样地,我们有另外两个保存了不同函数的文件: Phone/Isdn.py 含有函数Isdn() Phone/G3.py 含有函数G3() 现

  • 详解python程序中的多任务

    现实生活中,有很多场景中的事情是同时进行的,比如开车的时候,手和脚共同来驾驶汽车,再比如唱歌跳舞也是同时进行的. 以上这些可以理解为多任务.那在程序中怎么能做到多任务,它有什么好处? 接下来我们来看看没有多任务的程序是什么效果. import time def sing(): for i in range(5): print("正在唱...") time.sleep(1) def dance(): for i in range(5): print("正在跳...")

  • 详解python数组中的符号...与:符号的不同之处

    不知道大家有没有见过在python数组中使用...符号,因为前段时间读别人代码的时候遇到了这个符号立刻就云里雾里,于是这里特此记录一下.先来看一段代码: import numpy as np x = np.array([[1, 3], [5, 6], [8, 10]]) print("使用'...'符号的结果为:") print(x[..., 0]) print("使用':'符号的结果为:") print(x[:, 0]) """ 使用

  • 详解python requests中的post请求的参数问题

    问题:最新在爬取某站点的时候,发现在post请求当中,参数构造正确却获取不到数据,索性将post的参数urlencode之后放到post请求的url后面变成get请求,结果成功获取到数据,对此展开疑问. 1.http请求中Form Data和Request Playload的区别: Ajax post请求中常用的两种参数形式:form data 和 request payload get请求的时候,我们的参数直接反映在url里面,为key1=value1&key2=value2形式,如果是pos

  • 详解Python自动化中这八大元素定位

    一.find_element_by_id() find_element_by_id() 1.从上面定位到的元素属性中,可以看到有个id属性:id="kw",这里可以通过它的id属性定位到这个元素. 2.定位到搜索框后,用send_keys()方法,就可以输入文本. from selenium import webdriver driver = webdriver.Firefox() driver.get("http://www.baidu.com") # 通过id定

  • 详解python编程slice与indices函数用法示例

    一般来说,内置的slice()函数会创建一个切片对象,可以用在任何允许进行切片操作的地方. 下面是slice的简介: # slice 两种用法 class slice(stop) class slice(start, stop[, step]) 返回一个表示由 range(start, stop, step) 所指定索引集的 slice对象. 其中 start 和 step 参数默认为 None. 切片对象具有仅会返回对应参数值(或其默认值)的只读数据属性 start, stop 和 step.

  • 详解Python NumPy中矩阵和通用函数的使用

    目录 一.创建矩阵 二.从已有矩阵创建新矩阵 三.通用函数 四.算术运算 在NumPy中,矩阵是 ndarray 的子类,与数学概念中的矩阵一样,NumPy中的矩阵也是二维的,可以使用 mat . matrix 以及 bmat 函数来创建矩阵. 一.创建矩阵 mat 函数创建矩阵时,若输入已为 matrix 或 ndarray 对象,则不会为它们创建副本. 因此,调用 mat() 函数和调用 matrix(data, copy=False) 等价. 1) 在创建矩阵的专用字符串中,矩阵的行与行之

随机推荐