什么是Hook,就是在一個已有的方法上加入一些鉤子,使得在該方法執行前或執行后另在做一些額外的處理,那么Hook技巧有什么作用以及我們為什么需要使用它呢,事實上如果一個項目在設計架構時考慮的足夠充分,模塊抽象的足夠合理,設計之初為以后的擴展預留了足夠的接口,那么我們完全可以不需要Hook技巧。但恰恰架構人員在項目設計之初往往沒辦法想的足夠的深遠,使得后續在擴展時深圳面臨重構的痛苦,這時Hook技巧似乎可以為我們帶來一記緩兵之計,通過對舊的架構進行加鉤子來滿足新的擴展需求。
下面我們就來看看如果進行Hook處理,我們按照Hook的對象的層級來逐一介紹
對類進行Hook
也就是說我們得鉤子需要監控到類的創建等操作,然后在此之前或之后做我們希望的操作
1、Hook類的創建
你可以在寫一個類的時候為其添加__metaclass__屬性
class Foo(Bar): __metaclass__ = something…
Python創建類的過程是這樣的:
Foo中有__metaclass__這個屬性嗎?如果是,Python會在內存中通過__metaclass__創建一個名字為Foo的類。如果Python沒有找到__metaclass__,它會繼續在Bar(父類)中尋找__metaclass__屬性,并嘗試做和前面同樣的操作。如果Python在任何父類中都找不到__metaclass__,它就會在模塊層次中去尋找__metaclass__,并嘗試做同樣的操作。如果還是找不到__metaclass__,Python就會用內置的type來創建這個類對象。
所以我們需要在給__metaclass__屬性的值是一個能夠創建一個類的東西,即一個繼承type的類。
比如:
Singleton就是一個能夠創建類的對象,因為它繼承了type
也正因為此,我們可以在Singleton這個類中去監控MyClass的創建過程
2、Hook實例屬性
這里我們需要操作的屬性是__getattribute__和__getattr__
object.__getattribute__(self, name) :無論訪問存在還是不存在的屬性都先訪問該方法
object.__getattr__(self, name) :當不存在__getattribute__方法或者引發了AttributeError異常時訪問該方法
可以看到,訪問已有屬性a時,__getattribute__被調用,訪問未定義的屬性aa時__getattribute__先被調用,接著__getattr__被調用
3、Hook類屬性
python描述符是一個“綁定行為”的對象屬性,在描述符協議中,它可以通過方法重寫屬性的訪問。這些方法有 __get__(), __set__(), 和__delete__()。如果這些方法中的任何一個被定義在一個對象中,這個對象就是一個描述符。
- self: Desc的實例對象,其實就是TestDesc的屬性x
- instance: TestDesc的實例對象,其實就是t
- owner: 即誰擁有這些東西,當然是 TestDesc這個類,它是最高統治者,其他的一些都是包含在它的內部或者由它生出來的
為了讓描述符能夠正常工作,它們必須定義在類的層次上。否則Python無法自動為你調用__get__和__set__方法。
而根據之前對類方法的說明,引用t.x的時候是否會先引用TestDesc的__getattribute__方法呢?答案是會的,其實訪問屬性時在python中真實的查找順序是這樣的:
1)__getattribute__(), 無條件調用
2)數據描述符(定義了__set__或__delete__的描述符):由1)觸發調用 (若人為的重載了該 __getattribute__() 方法,可能會導致無法調用描述符)
3)實例對象的字典
4)類的字典
5)非數據描述符(只定義了__get__的描述符)
6)父類的字典
7)__getattr__() 方法
4、使用修飾符來Hook類
我們使用singleton方法把MyClass修飾為了一個單例模式,同時我們也在singleton方法中實現了對MyClass實例過程的監控。
對方法進行Hook
1、修飾符來Hook方法
1)修飾不帶參數的方法
2)修飾帶參數的方法
3)使用帶參數的修飾符來修飾方法
其他Hook
1、Hook內建方法
上述操作使得my_open代替了python內置的open方法,故而我們可以使用我們自己的my_open方法來監控后續對open方法的調用了
2、Monkey Patch
實際上這是所有語言都會使用到的Hook技巧,往往在我們使用了第三方的包,希望在之上做一些擴展,但又不想改動原有的代碼時使用
多說一句
上述提到了修飾符的操作,那么我們在使用修飾符時有一些小技巧需要了解
1、使用functools
防止使用修飾器后函數簽名被改變
2、使用decorator模塊來做修飾器
3、使用類做修飾器
實際應用中很少遇到可以使用一個類作為修飾器,但實際上只要一個類實現了__call__方法,其就可以作為一個修飾器存在了,并且由于類的可操作性較方法更強大,所以類做修飾器也可以實現更豐富的特性。
下面留個示例深入理解
# -*- coding: utf-8 -*- # import pythoncom import pyHook def onMouseEvent(event): # 監聽鼠標事件 print "MessageName:",event.MessageName print "Message:", event.Message print "Time:", event.Time print "Window:", event.Window print "WindowName:", event.WindowName print "Position:", event.Position print "Wheel:", event.Wheel print "Injected:", event.Injected print "---" # 返回 True 以便將事件傳給其它處理程序 # 注意,這兒如果返回 False ,則鼠標事件將被全部攔截 # 也就是說你的鼠標看起來會僵在那兒,似乎失去響應了 return Truedef onKeyboardEvent(event): # 監聽鍵盤事件 print "MessageName:", event.MessageName print "Message:", event.Message print "Time:", event.Time print "Window:", event.Window print "WindowName:", event.WindowName print "Ascii:", event.Ascii, chr(event.Ascii) print "Key:", event.Key print "KeyID:", event.KeyID print "ScanCode:", event.ScanCode print "Extended:", event.Extended print "Injected:", event.Injected print "Alt", event.Alt print "Transition", event.Transition print "---" # 同鼠標事件監聽函數的返回值 return True def main(): # 創建一個“鉤子”管理對象 hm = pyHook.HookManager() # 監聽所有鍵盤事件 hm.KeyDown = onKeyboardEvent # 設置鍵盤“鉤子” hm.HookKeyboard() # 監聽所有鼠標事件 hm.MouseAll = onMouseEvent # 設置鼠標“鉤子” hm.HookMouse() # 進入循環,如不手動關閉,程序將一直處于監聽狀態 pythoncom.PumpMessages() if __name__ == "__main__": main()
#將test.py變為test.exe#Get py2exe from http://www.py2exe.org/ from distutils.core import setupimport py2exesetup(console=['test.py'])#cmd下執行:python setup.py py2exe,在dist目錄下有exe和必備dll
#隱藏控制臺,讓其一閃而過import ctypes whnd = ctypes.windll.kernel32.GetConsoleWindow() if whnd != 0: ctypes.windll.user32.ShowWindow(whnd, 0) ctypes.windll.kernel32.CloseHandle(whnd)

小編就先聊到這里,今天交流的內容都是硬知識,普通的開發過程中也許并不能使用的上,但了解這些知識對于編程能力的提高很有幫助,也能夠幫助你更深入的理解Python的機制。也希望大家多多支持VEVB武林網。
新聞熱點
疑難解答