什么是水猴子是什么补丁

11:44 by youxin, ... 阅读,
monkey patch (猴子补丁)&& 用来在运行时动态修改已有的代码,而不需要修改原始代码。
简单的monkey patch 实现:[python]&#coding=utf-8&def originalFunc():&&&& print 'this is original function!'&&&&&&def modifiedFunc():&&&& modifiedFunc=1&&&& print 'this is modified function!'&&&&&&def main():&&&& originalFunc()&&&&&&if __name__=='__main__':&&&& originalFunc=modifiedFunc&&&& main()&
python中所有的东西都是object,包括基本类型。查看一个object的所有属性的方法是:dir(obj)函数在python中可以像使用变量一样对它进行赋值等操作。查看属性的方法:&print locals()&print globals()&
当我们import一个module时,python会做以下几件事情
& &导入一个module& &将module对象加入到sys.modules,后续对该module的导入将直接从该dict中获得& &将module对象加入到globals dict中
当我们引用一个模块时,将会从globals中查找。这里如果要替换掉一个标准模块,我们得做以下两件事情
&&& 1.将我们自己的module加入到sys.modules中,替换掉原有的模块。如果被替换模块还没加载,那么我们得先对其进行加载,否则第一次加载时,还会加载标准模块。(这里有一个import hook可以用,不过这需要我们自己实现该hook,可能也可以使用该方法hook module import)&&& 2.如果被替换模块引用了其他模块,那么我们也需要进行替换,但是这里我们可以修改globals dict,将我们的module加入到globals以hook这些被引用的模块。
一篇文章:
What is 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。
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.
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)
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
## 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]
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基础(19)
属性在运行时的动态替换,叫做猴子补丁(Monkey Patch)。
为什么叫猴子补丁
属性的运行时替换和猴子也没什么关系,关于猴子补丁的由来网上查到两种说法:
1,这个词原来为Guerrilla Patch,杂牌军、游击队,说明这部分不是原装的,在英文里guerilla发音和gorllia(猩猩)相似,再后来就写了monkey(猴子)。
2,还有一种解释是说由于这种方式将原来的代码弄乱了(messing with it),在英文里叫monkeying about(顽皮的),所以叫做Monkey Patch。
猴子补丁的叫法有些莫名其妙,只要和“模块运行时替换的功能”对应就行了。
Monkey patch就是在运行时对已有的代码进行修改,达到hot patch的目的。
Eventlet中大量使用了该技巧,以替换标准库中的组件,比如socket。
首先来看一下最简单的monkey patch的实现。
1class Foo(object):2 & &def bar(self):3 & & & &print('Foo.bar')45def bar(self):6 & &print('Modified bar')78Foo().bar()9Foo.bar = bar10Foo().bar()11121314由于Python中的名字空间是开放,通过dict来实现,所以很容易就可以达到patch的目的。1,运行时动态替换模块的方法stackoverflow上有两个比较热的例子,
consider a class that has a method get_data.
This method does an external lookup (on a database or web API, for example),
and various other methods in the class call it. However, in a unit test,
you don't want to depend on the external data source - so you dynamically replace the get_data method with a stub that returns some fixed data.
假设一个类有一个方法get_data。这个方法做一些外部查询(如查询数据库或者Web API等),
类里面的很多其他方法都调用了它。
然而,在一个单元测试中,你不想依赖外部数据源。所以你用哑方法态替换了这个get_data方法,
哑方法只返回一些测试数据。
另一个例子引用了,Zope wiki上对Monkey Patch解释:
from SomeOtherProduct.SomeModule import SomeClass
def speak(self):
& &return &ook ook eee eee eee!&
SomeClass.speak = speak
还有一个比较实用的例子,很多代码用到 import json,后来发现ujson性能更高,如果觉得把每个文件的import json 改成 import ujson as json成本较高,或者说想测试一下用ujson替换json是否符合预期,只需要在入口加上:
import json
import ujson
def monkey_patch_json():
json.__name__ = 'ujson'
json.dumps = ujson.dumps
json.loads = ujson.loads
monkey_patch_json()
2,运行时动态增加模块的方法
这种场景也比较多,比如我们引用团队通用库里的一个模块,又想丰富模块的功能,除了继承之外也可以考虑用Monkey Patch。
个人感觉Monkey Patch带了便利的同时也有搞乱源代码优雅的风险。
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:10412次
排名:千里之外
原创:32篇
转载:19篇
(34)(10)(13)(2)(8)(2)(1)
(window.slotbydup = window.slotbydup || []).push({
id: '4740881',
container: s,
size: '200,200',
display: 'inlay-fix'详解Python编程中对Monkey Patch猴子补丁开发方式的运用
作者:有心故我在
字体:[ ] 类型:转载 时间:
Monkey Patch猴子补丁方式是指在不修改程序原本代码的前提下,通过添加类或模块等方式在程序运行过程中加入代码,下面就来进一步详解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。
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)
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
## 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]
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。
您可能感兴趣的文章:
大家感兴趣的内容
12345678910
最近更新的内容
常用在线小工具html/javascript/php/CSS(27)
上两篇介绍了原型对象和原型链:
JavaScript对象创建模式:
深入理解JavaScript的原型对象 :
原型对象是JavaScript模拟类并实现继承的灵魂。这一篇介绍两个典型的问题:原型污染和猴子补丁
原型污染 Prototype Pollution
先看个例子:
function Person() { }
//先定义个空函数(空函数也有对应的原型对象)
//原型对象中声明两个方法,一个count,一个otherFunc
Person.prototype.count = function() {
//count方法统计原型对象中有多少个属性和方法
var i = 0;
for (var prop in this) { i++; }
Person.prototype.otherFunc
= function() { };
//随便定义个空方法,起名叫otherFunc
var p = new Person();
p.name = &Jack&;
//为对象添加两个属性name和age
p.age = 32;
alert(p.count());
有了前两篇的基础,应该能明白为何最后结果为4,而不是2。对象p有两个属性name和age,而Person是个空函数,预想应该返回2才对。但实际结果返回了4,枚举时将对象属性(name,age)和原型对象中的方法(count,otherFunc)都算进去了。这就是原型污染。
原型污染是指当枚举条目时,可能会导致出现一些在原型对象中不期望出现的属性和方法。
上面这个例子只是抛砖引玉引出原型污染的概念,并不具备太多现实意义,一个更现实的例子:
var book = new Array();
book.name = &Love in the Time of Cholera&;
//《霍乱时期的爱情》看完后整个人生都在里面
book.author = &Garcia Marquez&;
//加西亚马尔克斯著。另推荐《百年孤独》,永远的马孔多
book.date = &1985&;
alert(book.name);
//Love in the Time of Cholera
定义个Array对象,用于管理书本。结果很正确,看似没什么问题,但这个代码很脆弱,一不小心就会遇到原型污染的问题:
//为Array增加两个方法,first和last(猴子补丁后面会介绍)
Array.prototype.first = function() {
//获取第一个
return this[0];
Array.prototype.last = function() {
//获取最后一个
return this[this.length-1];
var bookAttributes = [];
//定义个book的属性的数组
for (var v in book) {
//将上面创建的Array对象book中属性一个个取出来,加入数组中
bookAttributes.push(v);
alert(bookAttributes);
//name,author,date,first,last
我们定义了个book对象,里面有name书名,author作者,date出版日这3个属性。通过枚举将3个属性加入到bookAttributes数组中后,发现不仅这3个属性,连Array的原型对象中的方法也被加入到了数组中了,这不是我们希望看到的
你可以用hasOwnProperty方法,来测试属性是否来自对象而非来自原型对象:
var bookAttributes = [];
for (var v in book) {
if(dict.hasOwnProperty(v)){
//为每个属性加上hasOwnProperty的测试
bookAttributes.push(v);
//只有对象自身的属性才会被加入数组
alert(bookAttributes);
//name,author,date
当然更好的方式应该是仅仅将Object的直接实例作为字典,而非Array,或Object的子类(如上述Person,函数本身也是Object):
var book = {};
//等价于var book = new Object(),不是new Array()
book.name = &Love in the Time of Cholera&;
book.author = &Garcia Marquez&;
book.date = &1985&;
var bookAttributes = [];
for (var v in book) {
bookAttributes.push(v);
alert(bookAttributes);
// name,author,date 这样就避免了原型污染
当然你可能疑惑:仍旧可以像在Array.prototype中加入猴子补丁一样,在Object.prototype中增加属性,这样不还是会导致原型污染吗?确实如此,但Object对象是JavaScript的根对象,即便技术上能够实现,你也永远不要对Object对象做任何修改。
如果你是做业务项目,上述这些已经足以让你避免原型污染问题了。不过如果你要开发通用的库,还需要考虑些额外的问题。
比如,你的库中提供has方法,能判断对像中是否有该属性(非来自原型对象的属性),你可能这么做:
function Book(elements) {
this.elements = elements || {};
Book.prototype.has = function(key) {
return this.elements.hasOwnProperty(key);
var b = new Book({
name : &Love in the Time of Cholera&,
author : &García Márquez&,
date : &1985&
alert(b.has(&author&));
alert(b.has(&has&));
你在Book的原型对象中添加了has方法,判断传入的属性是否是对象自身的属性,如果是,返回true,如果不是(比如来自原型对象的属性)则返回false。结果表明author来自对象,因此返回了true,而has来自原型对象,因此返回了false。
一切都很完美,但万一有人在对象中有一个自定义的同名的hasOwnProperty属性,这将覆盖掉ES5提供的Object.hasOwnProperty。当然你会认为绝不可能有人会将一个属性起名为hasOwnProperty。但作为通用接口,你最好不做任何假设,可以用call方法改进:
Book.prototype.has = function(key) {
return {}.hasOwnProperty.call(this.elements, key);
运行结果和改进前一样,没有任何区别,但现在就算有人在对象中定义了同名的hasOwnProperty属性,has方法内仍旧会正确调用ES5提供的Object.hasOwnProperty方法。
猴子补丁 Monkey-Patching
猴子补丁的吸引力在于方便,数组缺少一个有用的方法?加一个就是了:
Array.prototype.split = function(i) {
return [this.slice(0, i), this.slice(i)];
环境太旧,不支持ES5中Array的新方法如forEach,map,filter?加上就是了:
if (typeof Array.prototype.map !== &function&) {
//确保如存在的话,它不被覆盖
Array.prototype.map = function(f, thisArg) {
var result = [];
for (var i = 0, n = this. i & i++) {
result[i] = f.call(thisArg, this[i], i);
但是当多个库给同一原型打猴子补丁时会出现问题,如项目中依赖的另一个库也有个Array的split方法,但和上面的实现不同:
Array.prototype.split = function() {
var i = Math.floor(this.length / 2);
return [this.slice(0, i), this.slice(i)];
现在对Array调用split方法会有50%的几率出错,这取决于哪个库哪个版本先被加载(假设它们之间没有依赖的先后顺序)被调用。
解决方案是,将想要的版本封装起来:
function addArrayMethods() {
Array.prototype.split = function(i) {
return [this.slice(0, i), this.slice(i)];
需要调用split方法时,改为调用封装函数可以避免错误。
以上转自:
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:129365次
积分:1995
积分:1995
排名:千里之外
原创:40篇
转载:218篇
(2)(1)(5)(3)(2)(3)(1)(4)(13)(10)(9)(5)(16)(11)(24)(15)(10)(1)(8)(1)(8)(4)(15)(2)(25)(7)(12)(3)(7)(3)(1)(2)(1)(3)(22)
(window.slotbydup = window.slotbydup || []).push({
id: '4740881',
container: s,
size: '200,200',
display: 'inlay-fix'

我要回帖

更多关于 python 猴子补丁 的文章

 

随机推荐