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

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

文章来自https://www.jb51.net/article/127310.htm

这篇文章主要介绍了详解Python开发中如何使用Hook技巧,详细的介绍了Python Hook的用法和示例,有兴趣的可以了解一下

什么是Hook,就是在一个已有的方法上加入一些钩子,使得在该方法执行前或执行后另在做一些额外的处理,那么Hook技巧有什么作用以及我们为什么需要使用它呢,事实上如果一个项目在设计架构时考虑的足够充分,模块抽象的足够合理,设计之初为以后的扩展预留了足够的接口,那么我们完全可以不需要Hook技巧。但恰恰架构人员在项目设计之初往往没办法想的足够的深远,使得后续在扩展时深圳面临重构的痛苦,这时Hook技巧似乎可以为我们带来一记缓兵之计,通过对旧的架构进行加钩子来满足新的扩展需求。

下面我们就来看看如果进行Hook处理,我们按照Hook的对象的层级来逐一介绍

对类进行Hook

也就是说我们得钩子需要监控到类的创建等操作,然后在此之前或之后做我们希望的操作

1、Hook类的创建

你可以在写一个类的时候为其添加__metaclass__属性

class Foo(Bar): __metaclass__ = something…

ython创建类的过程是这样的:

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._instance
class 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 name

c = C()
c.a
c.aa

运行结果:

__getattribute__() is called
__getattribute__() is called
__getattr__() is called

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

3、Hook类属性

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

class Desc(object):
   def __get__(self, instance, owner):
      print("__get__...")
      print(self)
      print(instance)
      print(owner)

   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

@singleton
class 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

@something
def func():
   pass

2)修饰带参数的方法

代码如下:

def something(func): 
      def wrap(*args,**kargv):
            print("start")
            func(*args,**kargv)
            print("end")
      return wrap

@something
def func(a,b): 
   pass

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

def something(a,b): 
   def new_func(func):
      def wrap(*args,**kargv): 
         print("a") 
         func(*args,**kargv) 
         print("b")
      return 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 SomeClass

def speak(self): 
   return "ookookeeeeeeeee!"
SomeClass.speak = speak

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

多说一句

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

1、使用functools

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

代码如下:

from functools import wraps
def my_dec(func):
   @wraps(func)
   def wrapped():
      print ("siscalled")
      func.__name__
      return func() 
   return wrapped
@my_dec
def foo(): 
   pass


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

 

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

代码如下:

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

3、使用类做修饰器

复制代码 代码如下:

class test(object): 
   def __init__(self,func): 
      self._func = func 
   def    __call__(self): 
      print("start") 
      self._func() 
      print ("end")
      
@test
def func(): 
   print(func)
   
func()

运行结果:

start
func
end

实际应用中很少遇到可以使用一个类作为修饰器,但实际上只要一个类实现了__call__方法,其就可以作为一个修饰器存在了,并且由于类的可操作性较方法更强大,所以类做修饰器也可以实现更丰富的特性。

下面留个示例深入理解

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

# -*- 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()

?

1

2

3

4

5

6

7

8

9

#将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

?

1

2

3

4

5

6

#隐藏控制台,让其一闪而过

import ctypes

whnd = ctypes.windll.kernel32.GetConsoleWindow()

if whnd != 0:

  ctypes.windll.user32.ShowWindow(whnd, 0)

  ctypes.windll.kernel32.CloseHandle(whnd)

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值