需求不符合
很多時候,我們下載的?第三方庫?是不會有需求不滿足的情況,但也有極少的情況,第三方庫?沒有兼顧到需求,導致開發者無法實現相關功能。
如何通過一些操作將?第三方庫?源碼進行修改,是我們將要遇到的一個難點。接下來,本文將介紹幾個修改源碼的操作,看看你有實現過幾個?
本文可操作的是?有源碼的第三方庫,非源碼的不在本文討論范圍內。
模擬示例?
# -*- coding: utf-8 -*-
import?threading
import?timeclass?Proxy:def?__init__(self):# 這個線程是為了模擬網絡代理抓包后的發送任務,是測試用的self.simulate_thread = threading.Thread(target=self.run, args=())self.simulate_thread.start()self.lock = threading.Lock()self.target =?Nonedef?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?sysclass?VariableTracer:def__init__(self):# 用來保存局部變量self.vars?=?Nonedef?trace(self, func, *args, **kwargs):old_profile = sys.getprofile()# 設置新的 profiling 函數為我們自定義函數sys.setprofile(self.profiling)# 調用需要監聽的函數func(*args, **kwargs)# 將以前的 profiling 函數 更換回去sys.setprofile(old_profile)returnself.varsdef?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>'}
-
優點?:不需要修改源碼文件和重復實現源碼邏輯
-
缺點?:如果源碼耗時復雜,可能會有性能問題
結尾
修改源碼文件邏輯的事情可能發生的頻率不是很高,但真正遇到時那就非常糟心,本文使用了四種方式,如果你還有更好的方式請留言告訴我吧。
如果這篇文章對你有幫助,點個贊讓我知道哦!