详解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从ftp下载数据保存实例
Nov 20 Python
忘记ftp密码使用python ftplib库暴力破解密码的方法示例
Jan 22 Python
Python Tkinter基础控件用法
Sep 03 Python
Python使用shelve模块实现简单数据存储的方法
May 20 Python
OpenCV实现人脸识别
Apr 07 Python
Python基于scapy实现修改IP发送请求的方法示例
Jul 08 Python
致Python初学者 Anaconda入门使用指南完整版
Apr 05 Python
python实现读取大文件并逐行写入另外一个文件
Apr 19 Python
python接口自动化(十六)--参数关联接口后传(详解)
Apr 16 Python
Pandas中resample方法详解
Jul 02 Python
python使用for...else跳出双层嵌套循环的方法实例
May 17 Python
pycharm 如何取消连按两下shift出现的全局搜索
Jan 15 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处理大量表单字段的便捷方法
2015/02/07 PHP
标准PHP的AES加密算法类
2015/03/12 PHP
基于命令行执行带参数的php脚本并取得参数的方法
2016/01/25 PHP
(currentStyle)javascript为何有时用style得不到已设定的CSS的属性
2007/08/15 Javascript
JavaScipt基本教程之JavaScript语言的基础
2008/01/16 Javascript
JavaScript 面向对象的 私有成员和公开成员
2010/05/13 Javascript
从盛大通行证上摘下来的身份证验证js代码
2011/01/11 Javascript
jquery实现excel导出的方法
2013/04/04 Javascript
JavaScript实现N皇后问题算法谜题解答
2014/12/29 Javascript
script标签属性用type还是language
2015/01/21 Javascript
jQuery实现简单的手风琴效果
2020/04/17 jQuery
JavaScript用200行代码制作打飞机小游戏实例
2017/06/21 Javascript
微信小程序wx.previewImage预览图片实例详解
2017/12/07 Javascript
[39:08]完美世界DOTA2联赛PWL S3 LBZS vs CPG 第一场 12.12
2020/12/16 DOTA
Python 字符串定义
2009/09/25 Python
Python实现的彩票机选器实例
2015/06/17 Python
python dict.get()和dict['key']的区别详解
2016/06/30 Python
Python实现批量检测HTTP服务的状态
2016/10/27 Python
解决python删除文件的权限错误问题
2018/04/24 Python
python实现事件驱动
2018/11/21 Python
django settings.py 配置文件及介绍
2019/07/15 Python
python求平均数、方差、中位数的例子
2019/08/22 Python
Python 音频生成器的实现示例
2019/12/24 Python
Python startswith()和endswith() 方法原理解析
2020/04/28 Python
Python制作一个仿QQ办公版的图形登录界面
2020/09/22 Python
css3 仿写阿里云水纹效果的示例代码
2018/02/10 HTML / CSS
美国知名日用品连锁超市:Dollar General(多来店)
2017/01/14 全球购物
Rowdy Gentleman服装和配饰:美好时光
2019/09/24 全球购物
体育教师个人的自我评价
2014/02/16 职场文书
结对共建工作方案
2014/06/02 职场文书
2014年小学重阳节活动策划方案
2014/09/16 职场文书
2015商场元旦促销活动策划方案
2014/12/09 职场文书
东京审判观后感
2015/06/01 职场文书
保险公司增员口号
2015/12/25 职场文书
nginx服务器的下载安装与使用详解
2021/08/02 Servers
python index() 与 rindex() 方法的使用示例详解
2022/12/24 Python