需求不符合
很多时候,我们下载的 第三方库 是不会有需求不满足的情况,但也有极少的情况,第三方库 没有兼顾到需求,导致开发者无法实现相关功能。
如何通过一些操作将 第三方库 源码进行修改,是我们将要遇到的一个难点。接下来,本文将介绍几个修改源码的操作,看看你有实现过几个?
本文可操作的是 有源码的第三方库,非源码的不在本文讨论范围内。
模拟示例
# -*- coding: utf-8 -*-
import threading
import time
class Proxy:
def __init__(self):
# 这个线程是为了模拟网络代理抓包后的发送任务,是测试用的
self.simulate_thread = threading.Thread(target=self.run, args=())
self.simulate_thread.start()
self.lock = threading.Lock()
self.target = None
def run(self):
while True:
time.sleep(1)
with self.lock:
if self.target is not None:
self.target(self.parse(None))
def parse(self, data):
'''
模拟 解析二进制数据并转为字典
:param data:
:return:
'''
result = {
'host': '127.0.0.1',
'content_type': 'text/html',
'body': '<html></html>'
}
return result['body']
def hook(self, target):
'''
模拟挂载方法
:param target:
:return:
'''
with self.lock:
self.target = target
上面代码将模拟一个网络代理,我们将其取名为 Proxy 库,这个网络代理可以捕获 接口二进制数据 ,并返回一个 内容 给开发者。
该网络代理的作者虽然得到了一个比较全的数据,但只返回了 body 给使用者,而现在我们需要获取 host 的内容,所以要进行修改源码来获取。
下面是我们调用的代码:
def get_hook_data(data):
print(data)
p = Proxy()
p.hook(target=get_hook_data)
结果返回:
<html></html>
<html></html>
1. 修改源文件
这个方法应该是绝大部分开发者能想到的办法,由于 python 的第三方库绝大部分都是通过 pip 来安装的,我们可以通过找到 安装路径 的第三方库源码来修改。
例如我们假设上面的 Proxy 的源码安装在了 D:\Env\Project\Lib\site-packages\Proxy ,找到了源码文件 Proxy.py。

将源码的 parse() 方法直接进行修改:
def parse(self, data):
'''
模拟 解析二进制数据并转为字典
:param data:
:return:
'''
result = {
'host': '127.0.0.1',
'content_type': 'text/html',
'body': '<html></html>'
}
return {
'body': result['body'],
'host': result['host']
}
现在我们来看看返回结果:
{'body': '<html></html>', 'host': '127.0.0.1'}
{'body': '<html></html>', 'host': '127.0.0.1'}
{'body': '<html></html>', 'host': '127.0.0.1'}
{'body': '<html></html>', 'host': '127.0.0.1'}
优点 :简洁明了,非常直接
缺点 :当我们环境发生改变时,每次都需要修改源码,非常麻烦
2. 继承修改
继承修改 的方法比较适合大神,为什么这么说呢?假如我们的这个 二进制数据 解析方法非常非常麻烦,没有一定的了解很难解析,那么这个方法将会非常痛苦。
class MyProxy(Proxy):
def parse(self, data):
# 这里需要我们自己重新实现第三方库的逻辑
result = {
'host': '127.0.0.1',
'content_type': 'text/html',
'body': '<html></html>'
}
return {
'body': result['body'],
'host': result['host']
}
我们继承了原来 第三方库 的 类 ,然后通过继承覆写来修改方法的返回值,现在我们可以通过调用 继承 类来实现需求:
def get_hook_data(data):
print(data)
p = MyProxy()
p.hook(target=get_hook_data)
返回结果:
{'body': '<html></html>', 'host': '127.0.0.1'}
{'body': '<html></html>', 'host': '127.0.0.1'}
优点 :不需要修改源码文件
缺点 :当源码逻辑非常复杂时,重新去实现逻辑比较困难;如果源码中存在大量调用其他模块的,需要一模一样 import 过来,工作量比较大
额外提供一个方法来减少 继承 实现难度:我们可以通过复制 源码 文件原有逻辑来进行继承,这样会减少很多工作量。
3. 猴子补丁
猴子补丁可以在运行时修改类,通过它我们也可以改写方法,但和继承类似,通过它进行修改也免不了重新实现源码逻辑:
def my_parse(self, data):
# 这里需要我们自己重新实现第三方库的逻辑
result = {
'host': '127.0.0.1',
'content_type': 'text/html',
'body': '<html></html>'
}
return {
'body': result['body'],
'host': result['host']
}
Proxy.parse = my_parse
正常调用:
p = Proxy()
p.hook(target=get_hook_data)
返回结果:
{'body': '<html></html>', 'host': '127.0.0.1'}
{'body': '<html></html>', 'host': '127.0.0.1'}
优点 :不需要修改源码文件
缺点 :缺点和 继承修改 类似
4. 追踪局部变量
接下来,我们将需要一点 黑魔法 来实现。
众所周知在 PyCharm 进行断点运行时,可以在断点处来获取 局部和全局变量,那么我们是否可以用代码来做到这一点呢?
答案是可以,请看代码:
import sys
class VariableTracer:
def__init__(self):
# 用来保存局部变量
self.vars = None
def trace(self, func, *args, **kwargs):
old_profile = sys.getprofile()
# 设置新的 profiling 函数为我们自定义函数
sys.setprofile(self.profiling)
# 调用需要监听的函数
func(*args, **kwargs)
# 将以前的 profiling 函数 更换回去
sys.setprofile(old_profile)
returnself.vars
def profiling(self, frame, event, arg):
# 当方法调用 return 之前的局部变量
if event == 'return':
vars: dict = frame.f_locals
# 保存下来进行返回
self.vars = {key: value for key, value invars.items()}
class MyProxy(Proxy):
def parse(self, data):
vars = VariableTracer().trace(super(MyProxy, self).parse, data)
result = vars['result']
return {
'host': result['host'],
'body': result['body']
}
我们通过 sys.setprofile() 来设置一个自定义的 profiling函数,这个函数在以下事件发生时都会被解释器调用:
函数调用(call):当一个函数被调用时。
函数返回(return):当一个函数返回时。
异常抛出(exception):当一个异常被抛出时。
C 函数调用(c_call):当一个 C 函数被调用时(仅适用于某些情况)。
我们通过被调用的时机去获取局部变量,这样就可以更换返回值结果。
我们使用自定义类正常调用:
def get_hook_data(data):
print(f'hook {data}')
p = MyProxy()
p.hook(target=get_hook_data)
返回结果:
{'host': '127.0.0.1', 'body': '<html></html>'}
{'host': '127.0.0.1', 'body': '<html></html>'}
优点 :不需要修改源码文件和重复实现源码逻辑
缺点 :如果源码耗时复杂,可能会有性能问题
结尾
修改源码文件逻辑的事情可能发生的频率不是很高,但真正遇到时那就非常糟心,本文使用了四种方式,如果你还有更好的方式请留言告诉我吧。
如果这篇文章对你有帮助,点个赞让我知道哦!