一、什么是monkey patch
在网上也查了一下,关于这个名字起的比较随意,也勉强理解这样吧:
- 这个词原来叫Guerrilla Patch,杂牌军、游击队,说明这部分不是原装的,在英文里面guerilla发音和gorllia(猩猩)相似,再后来就写了monkey(猴子)。
- 猴子补丁(monkey patch)的主要功能就是动态属性的替换,和"模块运行时替换的功能"对应。
二、monkey patch的功能简介
monkey patch允许在运行期间动态修改一个类或模块(注意:python中一切皆对象,包括类、方法、甚至是模块)
举例:
2.1运行时动态改变类的方法
class A:
def func(self):
print('hi')
def monkey(self):
print('hi,monkey')
a = A()
a.func()
#运行结果:hi
a.func = a.monkey
a.func()
#运行结果:hi,monkey
- 其实这根本的原因在于python语法的灵活性,方便可以像普通对象那样使用。
还可以这么做:
class A:
def func(self):
print('hi')
def monkey(self):
print('hi,monkey')
def other_monkey(a):
print('hi,other monkey')
a = A()
A.func= other_monkey
a.func()
#运行结果:hi,other monkey
class A:
def __init__(self):
self.x = 3
self.y = 'abcdef'
def mul(self):
return self.y * self.x
def power(self, n):
return self.x ** n
A.power = power
a = A()
print(a.power(3))
#运行结果27
将类外面的普通方法依然可以在程序运行的时候动态赋值给类的某一个方法。
总结:monkey patch ,即运行时动态改变方法、类的方法。其实不管是定义在类外的普通方法、类里面的方法、甚至是模块这些都可以进行‘动态替换的操作’,感叹python真香。
2.2monkey patch的应用场景
这里举一个比较实用的例子,很多代码用到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()
- 它的底层原理是有一个__dict__属性,每一次的赋值都是在__dict__里加一个key值
2.3monkey patch实现处理save和get的方法
还有一种场景比较多见,比如我们引用团队通用库里的一个模块,又想丰富模块的功能,除了继承之外也可以考虑monkey patch。
实例:
from django.db import models
from django.db.models import query
from libs.cache import rds
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
"""
Save the current instance. Override this in a subclass if you want to
control the saving process.
The 'force_insert' and 'force_update' parameters can be used to insist
that the "save" must be an SQL insert or update (or equivalent for
non-SQL backends), respectively. Normally, they should not be set.
"""
#调用原save()方法将数据保存到数据库
self.save(force_insert, force_update, using, update_fields)
#将model_obj保存到缓存中
model_key = 'Model-%s-%s' %(self.__class__.__name__, self.pk) #pk:主键
rds.set(model_key, self, 86400 * 7) #缓存时间应该加一个随即值,防止数据库雪崩
def get(self, *args, **kwargs):
"""
Perform the query and return a single object matching the given
keyword arguments.
"""
#取出Model类的名称
cls_name = self.model.__name__
#检查kwargs中是否有id或pk
pk = kwargs.get('pk') or kwargs.get('id')
if pk is not None:
model_key = 'Model-%s-%s'%(cls_name, pk) #定义缓存key
model_obj = rds.get(model_key) #从redis中取出模型对象
if isinstance(model_obj, self.model): #判断model_obj是不是self,model的实例,检查数据正确性
return model_obj
#如果缓存中未取到model数据,则直接从数据库中获取
model_obj = self._get(*args, **kwargs)
#将取到的model对象保存到缓存
model_key = 'Model-%s-%s' % (cls_name, model_obj.pk)
rds.set(model_key, model_obj, 86400 * 7)
return model_obj
def patch_model():
'''通过Monkey patch的方式为DjangoORM增加缓存处理'''
#修改Model的save方法
models.Model._save = models.Model.save
models.Model.save = save
#修改get方法
query.QuerySet._get = query.QuerySet.get
query.QuerySet.get = get
- monkey patch 需要提前运行,所以这里把它放在了pymysql运行时的地方
import pymysql
from libs.orm import patch_model
pymysql.version_info = (1, 3, 13, "final", 0)
pymysql.install_as_MySQLdb()
patch_model()