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()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章