monkey patch(猴子补丁)

一、什么是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()
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

͝͝͝͝͝͝͝͝͝͝L Y H

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值