详解Python开发中如何使用Hook技巧


Posted in Python onNovember 01, 2017

什么是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的类。

比如:

class Singleton(type): def__init__(cls, name, bases, dict): super(Singleton, cls).__init__(name, bases, dict) cls._instance = None def__call__(cls, *args, **kw): if cls._instance is None: cls._instance = super(Singleton, cls).__call__(*args, **kw) return cls._instanceclass MyClass(object): __metaclass__ = Singleton

Singleton就是一个能够创建类的对象,因为它继承了type

也正因为此,我们可以在Singleton这个类中去监控MyClass的创建过程

2、Hook实例属性

这里我们需要操作的属性是__getattribute__和__getattr__

object.__getattribute__(self, name) :无论访问存在还是不存在的属性都先访问该方法

object.__getattr__(self, name) :当不存在__getattribute__方法或者引发了AttributeError异常时访问该方法

class C(object): a = 'abc' def __getattribute__(self, *args, **kwargs): print(__getattribute__() is called) return object.__getattribute__(self, *args, **kwargs) def __getattr__(self, name): print(__getattr__() is called) return namec = C()print c.a__getattribute__() is calledabcprint c.aa__getattribute__() is called__getattr__() is calledaa

可以看到,访问已有属性a时,__getattribute__被调用,访问未定义的属性aa时__getattribute__先被调用,接着__getattr__被调用

3、Hook类属性

python描述符是一个“绑定行为”的对象属性,在描述符协议中,它可以通过方法重写属性的访问。这些方法有 __get__(), __set__(), 和__delete__()。如果这些方法中的任何一个被定义在一个对象中,这个对象就是一个描述符。

class Desc(object): def __get__(self, instance, owner):print(__get__...) def __set__(self, instance, value):print('__set__...')class TestDesc(object): x = Desc()t = TestDesc()t.x__get__...

- 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类

def singleton(cls, *args, **kw): instances = {} def _singleton(): if cls not in instances: instances[cls] = cls(*args, **kw) return instances[cls] return _singleton@singletonclass MyClass(object): a = 1 def __init__(self, x=0): self.x = x

我们使用singleton方法把MyClass修饰为了一个单例模式,同时我们也在singleton方法中实现了对MyClass实例过程的监控。

对方法进行Hook

1、修饰符来Hook方法

1)修饰不带参数的方法

def something(func): def wrap(): print start func() print end return wrap@somethingdef func(): pass

2)修饰带参数的方法

def something(func): defwrap(*args,**kargv):print startfunc(*args,**kargv)print end return wrap@somethingdef func(a,b): pass

3)使用带参数的修饰符来修饰方法

def something(a,b): def new_func(func):def wrap(*args,**kargv): print a func(*args,**kargv) print breturn wrap return new_func@something(1,2)def func(a,b): pass

其他Hook

1、Hook内建方法

#Hookopen方法real_open = __builtins__.open__builtin__.open = my_open#Hookimport方法real_importer = __import____builtins__.__import__ = my_importer

上述操作使得my_open代替了python内置的open方法,故而我们可以使用我们自己的my_open方法来监控后续对open方法的调用了

2、Monkey Patch

from SomeOtherProduct.SomeModule import SomeClassdef speak(self): return "ookookeeeeeeeee!"SomeClass.speak = speak

实际上这是所有语言都会使用到的Hook技巧,往往在我们使用了第三方的包,希望在之上做一些扩展,但又不想改动原有的代码时使用

多说一句

上述提到了修饰符的操作,那么我们在使用修饰符时有一些小技巧需要了解

1、使用functools

防止使用修饰器后函数签名被改变

from functools import wrapsdef my_dec(func): @wraps(func) def wrapped():print %siscalled%func.__name__return func() return wrapped@my_decdef foo(): pass

这样处理后,foo方法的签名与被修饰之前保持了一致,否则签名将会变成my_dec方法的签名

2、使用decorator模块来做修饰器

from decorator import decorator@decoratordef wrap(f,*args,**kw): print start f(*args,**kw) print end#这样wrap方法就变成了一个decorator@wrapdef func(): print func

3、使用类做修饰器

class test(object): def__init__(self,func): self._func = func def__call__(self): print start self._func() print end@testdef func(): print funcfunc()startfuncend

实际应用中很少遇到可以使用一个类作为修饰器,但实际上只要一个类实现了__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 True

def 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 setup
import py2exe

setup(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开发中如何使用Hook技巧

小编就先聊到这里,今天交流的内容都是硬知识,普通的开发过程中也许并不能使用的上,但了解这些知识对于编程能力的提高很有帮助,也能够帮助你更深入的理解Python的机制。也希望大家多多支持三水点靠木。

Python 相关文章推荐
python网络编程学习笔记(七):HTML和XHTML解析(HTMLParser、BeautifulSoup)
Jun 09 Python
使用Python中的tkinter模块作图的方法
Feb 07 Python
Python在图片中插入大量文字并且自动换行
Jan 02 Python
python networkx 根据图的权重画图实现
Jul 10 Python
pytorch numpy list类型之间的相互转换实例
Aug 18 Python
Django之PopUp的具体实现方法
Aug 31 Python
浅谈tensorflow 中tf.concat()的使用
Feb 07 Python
基于Python实现视频的人脸融合功能
Jun 12 Python
python通过函数名调用函数的几种场景
Sep 23 Python
Python中Permission denied的解决方案
Apr 02 Python
Python 使用dict实现switch的操作
Apr 07 Python
用Python监控你的朋友都在浏览哪些网站?
May 27 Python
python利用标准库如何获取本地IP示例详解
Nov 01 #Python
你眼中的Python大牛 应该都有这份书单
Oct 31 #Python
Python生成数字图片代码分享
Oct 31 #Python
python使用标准库根据进程名如何获取进程的pid详解
Oct 31 #Python
Python列表删除的三种方法代码分享
Oct 31 #Python
Python文件的读写和异常代码示例
Oct 31 #Python
Python网络编程详解
Oct 31 #Python
You might like
用PHP制作静态网站的模板框架(三)
2006/10/09 PHP
thinkphp四种url访问方式详解
2014/11/28 PHP
10条php编程小技巧
2015/07/07 PHP
javascript getElementsByClassName 和js取地址栏参数
2010/01/02 Javascript
date.parse在IE和FF中的区别
2010/07/29 Javascript
网页整体变灰白色(兼容各浏览器)实例
2013/04/21 Javascript
jQuery响应鼠标事件并隐藏与显示input默认值
2014/08/24 Javascript
jquery插件corner实现圆角边框的方法
2015/03/09 Javascript
jquery代码实现简单的随机图片瀑布流效果
2015/04/20 Javascript
浅析node连接数据库(express+mysql)
2015/11/30 Javascript
BootStrap的Datepicker控件使用心得分享
2016/05/25 Javascript
星期几的不同脚本写法(推荐)
2016/06/01 Javascript
redux-saga 初识和使用
2018/03/10 Javascript
基于Vue 服务端Cookies删除的问题
2018/09/21 Javascript
微信小程序中button去除默认的边框实例代码
2019/08/01 Javascript
在Chrome DevTools中调试JavaScript的实现
2020/04/07 Javascript
Python函数式编程指南(四):生成器详解
2015/06/24 Python
python实现八大排序算法(2)
2017/09/14 Python
tensorflow1.0学习之模型的保存与恢复(Saver)
2018/04/23 Python
Python 查找字符在字符串中的位置实例
2018/05/02 Python
python 对dataframe下面的值进行大规模赋值方法
2018/06/09 Python
Python vtk读取并显示dicom文件示例
2020/01/13 Python
使用python创建Excel工作簿及工作表过程图解
2020/05/27 Python
使用Keras中的ImageDataGenerator进行批次读图方式
2020/06/17 Python
python上下文管理器异常问题解决方法
2021/02/07 Python
新西兰廉价汽车租赁:Snap Rentals
2018/09/14 全球购物
UNIONBAY官网:美国青少年服装品牌
2019/03/26 全球购物
意大利巧克力店:Chocolate Shop
2019/07/24 全球购物
开业庆典邀请函
2014/01/08 职场文书
2014年党的群众路线教育实践活动总结
2014/04/25 职场文书
诉讼财产保全担保书
2014/05/20 职场文书
会计求职自荐信
2014/06/20 职场文书
2015毕业生实习期工作总结
2015/04/09 职场文书
小学运动会入场词
2015/07/18 职场文书
高效笔记技巧分享:学会这些让你不再困扰
2019/09/04 职场文书
Pytorch distributed 多卡并行载入模型操作
2021/06/05 Python