Monkey Patching and adding method dynamically

Monkey Patching

Summary

The concept

Monkey patching is a technique that is adopted to dynamically update the behavior of a piece of code at runtime

Why use it?

It allows us to modify or extend the behavior of libraries, modules, classes or methods at runtime without actually modifying the source code

When use it?

Common cases incl:

  • To extend or modify the behavior of built-in or third-party libraries or methods at runtime without modifying the source code
  • When testing, mock the behavior of libraries, modules, classes or any objects
  • To quickly fix some issues if we don’t have time or resources to roll-out a proper fix to the original software

Monkey patching should be used carefully

Reasons incl:

  • If we change the behavior of a method by monkey patching, it no longer behaves they way as it is documented. Users/clients should be aware of this change because it could cause their code to behave unexpectedly
  • It makes it more difficult to troubleshoot issues
  • If we monkey patch a method in one module and another module is using that same method after the patch is applied, then the second module will use the patched method instead of the original code. This may lead to unwante bugs

Exmaples

Example 1: Monkey patch the value of a module’s attribute

import math
ori_pi = math.pi
math.pi = round(ori_pi)
print(math.pi)
# 3.14
math.pi = ori_pi

Finally remove the patch and restore the original value of pi

Example 2: Monkey patch to extend/change the behavior of a method

Update the built-in print in Python3 to include a timestamp:

import builtins, datetime

def custom_print(*args, **kwargs):
	ori_print(datetime.datetime.utcnow(), *args, **kwargs)

ori_print = builtins.print
builtins.print = custom_print
print('this is a patched print')
# 2019-10-07 10:46:56.091853 this is a patched print
builtins.print = ori_print

Example 3: Monkey patching class/instance attributes

Patching class attributes:

class Power:
    def get(self, a, b):
        return a ** b

class myPower(Power):
    def mock_power(self, a, b):
        res = a ** b
        return f'result: {res}'

Power.get = myPower.mock_power
print(Power().get(2, 4))
# result: 16

Patch specific instance attributes (cont. above classes):

import types
p1 = Power()
p2 = Power()
p1.get = types.MethodType(myPower.mock_power, p1)
print(p1.get(2,4)) # result - 16
print(p2.get(2,4)) # 16

types.MethodType method helps bind the patched method to one instance; which assures that other instances of the class are not affected

Example 4: Monkey patching a class/module

We slightly modify myPower class from the above:

class Power:
    def get(self, a, b):
        return a ** b

class myPower(Power):
    def get(self, a, b):
        res = a ** b
        return f'result: {res}'

print(Power().get(2,4)) # 16
Power = myPower
print(Power().get(2,4)) # result: 16

Adding method dynamically in Python

Here is the simplest way, adding a method to an object, with an example - extend the feature of math.log to add a math.log100 with 100 as the base:

import math

def log100(x):
	return math.log(x, 100)

def add_patch():
	math.log100 = log100

add_patch()
print(math.log100(10))
# 0.5

If we’d like to add a method that is visible to all instances and can access the attributes of the instance:

class Employee:
    def __init__(self, id):
        self.id = id

def examine_id(self):
    flag = 'Pass'
    for char in self.id:
        if not char.isdigit():
            flag = 'Fail'

    print(f"id: {flag}")

Employee.examine_id = examine_id
e1 = Employee('i235')
e2 = Employee('2357')
e1.examine_id()
e2.examine_id()

Without self the func is just a func.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值