Python實現(xiàn)無痛修改第三方庫源碼的方法詳解
需求不符合
很多時候,我們下載的 第三方庫 是不會有需求不滿足的情況,但也有極少的情況,第三方庫 沒有兼顧到需求,導致開發(fā)者無法實現(xiàn)相關功能。
如何通過一些操作將 第三方庫 源碼進行修改,是我們將要遇到的一個難點。接下來,本文將介紹幾個修改源碼的操作,看看你有實現(xiàn)過幾個?
本文可操作的是 有源碼的第三方庫,非源碼的不在本文討論范圍內。
模擬示例
# -*- coding: utf-8 -*- import threading import time class Proxy: def __init__(self): # 這個線程是為了模擬網絡代理抓包后的發(fā)送任務,是測試用的 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): ''' 模擬 解析二進制數(shù)據(jù)并轉為字典 :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 庫,這個網絡代理可以捕獲 接口二進制數(shù)據(jù) ,并返回一個 內容 給開發(fā)者。
該網絡代理的作者雖然得到了一個比較全的數(shù)據(jù),但只返回了 body 給使用者,而現(xiàn)在我們需要獲取 host 的內容,所以要進行修改源碼來獲取。
下面是我們調用的代碼:
def get_hook_data(data): print(data) p = Proxy() p.hook(target=get_hook_data)
結果返回:
<html></html>
<html></html>
1. 修改源文件
這個方法應該是絕大部分開發(fā)者能想到的辦法,由于 python 的第三方庫絕大部分都是通過 pip 來安裝的,我們可以通過找到 安裝路徑 的第三方庫源碼來修改。
例如我們假設上面的 Proxy 的源碼安裝在了 D:\Env\Project\Lib\site-packages\Proxy ,找到了源碼文件 Proxy.py。
源碼路徑
將源碼的 parse() 方法直接進行修改:
def parse(self, data): ''' 模擬 解析二進制數(shù)據(jù)并轉為字典 :param data: :return: ''' result = { 'host': '127.0.0.1', 'content_type': 'text/html', 'body': '<html></html>' } return { 'body': result['body'], 'host': result['host'] }
現(xiàn)在我們來看看返回結果:
{'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'}
優(yōu)點 :簡潔明了,非常直接
缺點 :當我們環(huán)境發(fā)生改變時,每次都需要修改源碼,非常麻煩
2. 繼承修改
繼承修改 的方法比較適合大神,為什么這么說呢?假如我們的這個 二進制數(shù)據(jù) 解析方法非常非常麻煩,沒有一定的了解很難解析,那么這個方法將會非常痛苦。
class MyProxy(Proxy): def parse(self, data): # 這里需要我們自己重新實現(xiàn)第三方庫的邏輯 result = { 'host': '127.0.0.1', 'content_type': 'text/html', 'body': '<html></html>' } return { 'body': result['body'], 'host': result['host'] }
我們繼承了原來 第三方庫 的 類 ,然后通過繼承覆寫來修改方法的返回值,現(xiàn)在我們可以通過調用 繼承 類來實現(xiàn)需求:
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'}
優(yōu)點 :不需要修改源碼文件
缺點 :當源碼邏輯非常復雜時,重新去實現(xiàn)邏輯比較困難;如果源碼中存在大量調用其他模塊的,需要一模一樣 import 過來,工作量比較大
額外提供一個方法來減少 繼承 實現(xiàn)難度:我們可以通過復制 源碼 文件原有邏輯來進行繼承,這樣會減少很多工作量。
3. 猴子補丁
猴子補丁可以在運行時修改類,通過它我們也可以改寫方法,但和繼承類似,通過它進行修改也免不了重新實現(xiàn)源碼邏輯:
def my_parse(self, data): # 這里需要我們自己重新實現(xiàn)第三方庫的邏輯 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'}
優(yōu)點 :不需要修改源碼文件
缺點 :缺點和 繼承修改 類似
4. 追蹤局部變量
接下來,我們將需要一點 黑魔法 來實現(xiàn)。
眾所周知在 PyCharm 進行斷點運行時,可以在斷點處來獲取 局部和全局變量,那么我們是否可以用代碼來做到這一點呢?
答案是可以,請看代碼:
import sys class VariableTracer: def__init__(self): # 用來保存局部變量 self.vars = None def trace(self, func, *args, **kwargs): old_profile = sys.getprofile() # 設置新的 profiling 函數(shù)為我們自定義函數(shù) sys.setprofile(self.profiling) # 調用需要監(jiān)聽的函數(shù) func(*args, **kwargs) # 將以前的 profiling 函數(shù) 更換回去 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函數(shù),這個函數(shù)在以下事件發(fā)生時都會被解釋器調用:
函數(shù)調用(call):當一個函數(shù)被調用時。
函數(shù)返回(return):當一個函數(shù)返回時。
異常拋出(exception):當一個異常被拋出時。
C 函數(shù)調用(c_call):當一個 C 函數(shù)被調用時(僅適用于某些情況)。
我們通過被調用的時機去獲取局部變量,這樣就可以更換返回值結果。
我們使用自定義類正常調用:
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>'}
優(yōu)點 :不需要修改源碼文件和重復實現(xiàn)源碼邏輯
缺點 :如果源碼耗時復雜,可能會有性能問題
結尾
修改源碼文件邏輯的事情可能發(fā)生的頻率不是很高,但真正遇到時那就非常糟心,本文使用了四種方式,如果你還有更好的方式請留言告訴我吧。
到此這篇關于Python實現(xiàn)無痛修改第三方庫源碼的方法詳解的文章就介紹到這了,更多相關Python修改第三方庫源碼內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Python3.7 讀取音頻根據(jù)文件名生成腳本的代碼
這篇文章主要介紹了Python3.7 讀取音頻根據(jù)文件名生成字幕腳本的方法,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-04-04Python HTMLTestRunner測試報告view按鈕失效解決方案
這篇文章主要介紹了Python HTMLTestRunner測試報告view按鈕失效解決方案,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-05-05Python中動態(tài)創(chuàng)建類實例的方法
在Java中我們可以通過反射來根據(jù)類名創(chuàng)建類實例,那么在Python我們怎么實現(xiàn)類似功能呢?其實在Python有一個builtin函數(shù)import,我們可以使用這個函數(shù)來在運行時動態(tài)加載一些模塊2017-03-03python3利用Dlib19.7實現(xiàn)人臉68個特征點標定
這篇文章主要為大家詳細介紹了python3利用Dlib19.7實現(xiàn)人臉68個特征點標定,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-02-02