Python中的钩子函数(hooks)介绍使用

什么是hook?

钩子函数,顾名思义,就是把我们自己实现的自定义函数在某一时刻挂接到目标挂载点上去执行。
1. hook函数,就是我们自己实现的函数,函数类型与挂载点匹配(返回值,参数列表)
2. 挂接,也就是hook或者叫注册(register),使得hook函数对目标可用
3. 目标挂载点,也就是挂我们hook函数的地方(我们想在这个目标点实现我们自己的功能)

hook是一种编程机制,与具体的编程语言无关。

为什么需要hook?

什么情况下需要实现hook?
—就是一个功能(类/方法)自身无法满足所有需求,那么可以通过hook 就提供扩展自身能力的可能

同一个需求平替实现

比如有个需求:获取每次请求后的请求状态码

方式1:原始做法直接在代码里增加

–不推荐,而且违反“开闭原则”

import requests
url = ' http://www.baidu.com'
r = requests.get(url)
print(f"status doce: {r.status_code}")

方式2:使用装饰器

import requests
# 定义装饰器
def print_status_code(func):
    def wrappers(*args, **kwargs):
        res = func(*args, **kwargs)
        print(f"Status code: {res.status_code}")
        return res
    return wrappers
@print_status_code
def send_request(url):
    res = requests.get(url)
    return res

url='https://httpbin.org/get'
send_request(url)

方式3:使用requests库自动hooks入参来使用钩子函数:将“获取请求状态的逻辑封装到一个函数中,在执行完请求后,就自动执行钩子函数"

import requests	没有参数
def custom_hooks(response,**kwargs):
	print(response.status_code)
def custom_hooks2(response,**kwargs):
	print(response.headers)
url = ' http://www.baidu.com'
response = requests.get(url, hooks={"response": [custom_hooks, custom_hooks2]})

钩子函数什么时候执行?

比如:response = requests.get(url, hooks={“response”: [custom_hooks, custom_hooks2]})
自定义的钩子函数在执行完请求后,执行钩子函数

实例:requests库的hooks介绍

requests提供了hook机制,让我们能够在请求得到响应之后,再去额外做一些自定义的操作,比如打印某些信息、修改响应内容等。

注意:

  1. requests库中钩子函数是在请求得到响应之后才去行的钩子函数
  2. 如下示例测试数据基于requests 2.25.1

requests库如何使用hooks来调用钩子函数?

import requests
# 钩子函数1
def print_url(r, **kwargs):
	print("raw_url "+r.url)
# 钩子函数2
def change_url(r, **kwargs):
	r.url = ' http://change.url'
	print("changed_url "+r.url)
	return r  # 其实没有这句话,也可以修改r.url,因为r是response对象而非普通数值,但requests官方似乎误认为回调函数一定要有return才能替换传入的数据
url = ' http://httpbin.org/cookies'
# 使用hooks形参来调用钩子函数
response = requests.get(url, hooks=dict(response=[print_url, change_url]))
print("result_url "+response.url)

注意:
1) 钩子函数中的**kwargs形参不能少,且必须是关键字参数
□ 因为在调用钩子函数时,requests.session.py实现了添加了相关参数 在这里插入图片描述2) requests.中的hook入参值类型必须是字典,且key必须是"response",value有多个时必须为list类型。
□ key必须是"response": 因为
①requests.hooks.py如下代码有校验
在这里插入图片描述
②requests.models.py有如下校验 在这里插入图片描述

requests库使用钩子函数时,如果钩子函数需要传递参数,如何向钩子函数传递参数?

有两种方式:
方式1: 使用偏函数(funtools.partial)过要注意,使用这种方式,不能适用于钩子函数有多个位置参数的情况,只适配与钩子函数有关键字参数的情况。所以要将钩子函数设计成def my_hook_func(response, **kwargs)这种形式。
因为当使用 functools.partial 结合 requests 库的钩子函数时,直接传递位置参数会干扰 requests 默认传递给钩子函数的参数顺序。 requests 库在调用钩子函数时,会将 response 对象作为第一个参数传入。如果使用 partial 传递位置参数,这些位置参数会占用钩子函数本来为 response 留下的位置,从而导致错误。
示例:

import requests
from functools import partial

# 定义钩子函数
def response_hook(response, **kwargs):
	print(f"所有关键字参数{kwargs}")
	parms1 = kwargs.get('parms1')
	parms2 = kwargs.get('parms2')
	print("额外参数2:", parms1)
	print("额外参数2:", parms2)
	print("响应状态码:", response.status_code)
	return response

# 使用 partial 来创建一个新的钩子函数,其中传入了关键字参数
hook_with_param = partial(response_hook, parms1='val1', parms2='val2')

# 进行请求,并传入钩子函数
response = requests.get('https://httpbin.org/get', hooks={'response': hook_with_param})

# 打印响应内容
print(response.text)

方式2:使用闭包
使用闭包比使用偏函数稍微麻烦一点,不过使用闭包能支持位置参数和关键字参数,这点要比偏函数好

import requests

# 定义一个外层函数来包裹钩子函数,从而传入额外的参数
def create_response_hook(extra_param, additional_arg, **kwargs2):
	def response_hook(response, *args, **kwargs):
		print(f"kwargs参数的值{kwargs}")
		print("响应状态码:", response.status_code)
		print("额外参数:", extra_param)
		print("额外位置参数:", additional_arg)
		print("额外关键字参数:", kwargs2)
		print("params1参数:", kwargs2.get("params1"))
		print("params2参数:", kwargs2.get("params2"))
		return response
	return response_hook

# 创建带有额外参数的钩子函数
hook_with_param = create_response_hook('额外的数据', '额外位置参数1', params1="val1", params2="val2")

# 进行请求,并传入钩子函数
response = requests.get('https://httpbin.org/get', hooks={'response': hook_with_param})

# 打印响应内容
print(response.text)


<think>嗯,用户想了解HOOK函数的定义、用法和实现。首先,我需要整理一下不同来源的信息。引用1提到了Vue中的Hook函数,用来管理状态,比如计数器。引用2分类了各种hook类型,比如pre-hook和post-hook。引用3讲的是libc中的内存分配hook,比如__malloc_hook,用户可以通过这些弱符号来替换自己的实现。引用4讨论了PyTorch的hook,用于在forward或backward时插入处理逻辑。 首先,定义部分需要综合不同场景下的HOOK概念。HOOK函数本质是拦截和处理特定事件,可以在不修改原代码的情况下扩展功能。比如在Vue中是状态管理,在PyTorch中是获取中间层输出,在libc中是监控内存分配。 然后,用法部分要分不同框架或环境。Vue的组合式API使用useCounter这样的函数,返回响应式状态和方法。PyTorch的register_forward_hook用于特征可视化,而libc的hook需要替换弱符号函数指针,比如__malloc_hook。 实现方面,不同平台机制不同。JavaScript/Vue利用闭包和响应式系统,C语言通过弱符号和函数指针,PyTorch则提供注册hook的API。需要强调HOOK的核心是回调函数和事件触发。 用户可能想知道HOOK的应用场景,比如调试、性能分析、扩展功能。还要注意不同实现中的注意事项,比如PyTorch的backward hook不能原地修改梯度,libc的hook需要谨慎处理避免循环调用。 生成的问题可能需要涵盖具体实现步骤、不同框架的例子、优缺点比较以及常见问题。确保回答覆盖定义、使用和实现,并结合例子说明,帮助用户全面理解HOOK函数在不同上下文中的应用。</think>### HOOK函数详解 --- #### **一、定义** HOOK函数是程序设计中用于**拦截/监听特定事件**的技术手段,通过**回调函数机制**在系统流程中插入自定义逻辑,分为两种实现形式: 1. **框架层**:通过API注册回调函数(如PyTorch/Vue) 2. **系统层**:修改函数指针或符号表(如C标准库内存管理)[^3][^4] 数学表达示例: 设原函数为$f(x)$,HOOK函数为$h(x)$,则拦截过程可表示为: $$ f_{hooked}(x) = h(f(x)) $$ --- #### **二、核心使用场景** | 类型 | 应用场景 | 技术特性 | |-------------|-----------------------------------|----------------------------------| | 前端框架 | Vue3组合式API状态管理[^1] | 响应式数据封装 | | 深度学习 | PyTorch特征可视化[^4] | 梯度/激活值捕获 | | 系统编程 | 内存分配监控(malloc/free) | 弱符号函数指针覆盖 | | 事件处理 | 网络请求拦截 | 请求/响应过滤 | --- #### **三、实现方式对比** **1. Vue组合式函数(高阶HOOK)** ```javascript // useCounter.js import { reactive, toRefs } from 'vue'; export function useCounter(initial = 0) { const state = reactive({ count: initial }); const methods = { increment: () => state.count++, decrement: () => state.count-- }; return { ...toRefs(state), ...methods }; } ``` *实现原理*:通过闭包封装响应式对象,返回可组合的方法集[^1] --- **2. C语言内存HOOK(系统级)** ```c // 覆盖malloc的HOOK实现 void *(*orig_malloc)(size_t) = NULL; void my_malloc_hook(size_t size) { printf("Allocating %zu bytes\n", size); return orig_malloc(size); } __malloc_hook = my_malloc_hook; // 修改全局函数指针[^3] ``` *注意事项*:需保存原始函数指针防止循环调用 --- **3. PyTorch特征捕获HOOK** ```python # 注册前向传播HOOK def forward_hook(module, input, output): print(f"Layer {module.__class__.__name__} output shape: {output.shape}") model.conv1.register_forward_hook(forward_hook) # [^4] ``` *数学表达*: 设卷积层计算为$y = W \ast x + b$,HOOK将捕获输出张量$y$ --- #### **四、关键技术要点** 1. **生命周期管理** Vue HOOK自动处理依赖清理,而PyTorch需手动移除HOOK: ```python handle.remove() # 显式释放HOOK ``` 2. **执行时序控制** - Pre-hook:在目标函数执行前触发[^2] $$ f_{pre}(x) \rightarrow h(x) \rightarrow f(x) $$ - Post-hook:在目标函数执行后触发[^4] $$ f(x) \rightarrow h(f(x)) $$ 3. **安全边界条件** 系统级HOOK需确保: $$ \forall x \in D, \|h(f(x)) - f(x)\| < \epsilon $$ ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值