python里面并没有防抖节流函数,
在图形GUI表单操作时容易误操作,
因此实现了JavaScript中的debounce工具函数,以及定时器 setTimeout函数,
如下,
基础版:只支持首次立即执行
进阶版:支持限定时间内最后再执行
import threading
import time
from functools import wraps
def debounce(wait):
"""
防抖装饰器:开始执行一次后,限定时间内不重复执行。
:param wait: 等待时间,单位为毫秒。
"""
def decorator(func):
last = 0
@wraps(func)
def wrapper(*args, **kwargs):
nonlocal last
now = time.time()
if (now - last) > (wait / 1000):
last = now
return func(*args, **kwargs)
return wrapper
return decorator
def debounce2(wait, immediate=True):
"""
防抖装饰器:开始执行一次后,限定时间内不重复执行。
:param wait: 等待时间,单位为毫秒。
:param immediate: 是否在首次调用时立即执行函数。
"""
def decorator(func):
args = None
kwargs = None
last = 0
timestamp = 0
timeout = None
result = None
def later():
nonlocal args, kwargs, last, timeout, timestamp, result
print('later', 111, args, kwargs)
# 计算距离上一次触发的时间间隔
last = time.time() - timestamp
# 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait
if last < (wait / 1000) and last > 0:
timeout = setTimeout(later, wait - last)
else:
timeout = None
# 如果不是立即执行,且当前没有定时器,则执行函数
if not immediate:
result = func(*args, **kwargs)
args = kwargs = None
def wrapper(*_args, **_kwargs):
nonlocal args, kwargs, timeout, timestamp, result
args = _args
kwargs = _kwargs
timestamp = time.time()
call_now = immediate and timeout is None
# 如果延时不存在,重新设定延时
if timeout is None:
timeout = setTimeout(later, wait)
if call_now:
result = func(*_args, **_kwargs)
kwargs = args = None
return result
return wrapper
return decorator
# 定时器函数
def setTimeout(func, delay):
t = threading.Timer(delay / 1000, func)
t.start()
# 取消定时器 .cancel()
return t
# 清理定时器函数
def clearTimeout(timer):
timer.cancel()
@debounce(300)
def search(keyword):
print(f"Searching for: {keyword}")
@debounce2(1000, False)
def search2(keyword):
print(f"search2: {keyword}")
if __name__ == "__main__":
start_time = time.time()
# 模拟用户输入
# search("python")
# search("java")
# search("javascript")
# time.sleep(2)
#
#
# def print_message():
# print('定时器2s后执行')
#
#
# timer = setTimeout(print_message, 2000)
# timer.cancel()
# search('111')
# search(222)
search2(666)
search2(777)
time.sleep(2)
search2(888)
search2(999)
search2(10)
# 执行需要测量时间的代码段
end_time = time.time()
execution_time = end_time - start_time
print(f"执行消耗时间:{execution_time}秒")
拓展:
js 防抖节流库
/**
* 防抖 返回函数连续调用时,空闲时间必须大于或等于 wait,func 才会执行
*
* @param {function} func 传入函数,最后一个参数是额外增加的this对象,.apply(this, args) 这种方式,this无法传递进函数
* @param {number} wait 表示时间窗口的间隔
* @param {boolean} immediate 设置为ture时,调用触发于开始边界而不是结束边界
* @return {function} 返回客户调用函数
*/
export const debounce_fn = function (func, wait, immediate) {
let timeout, args, context, timestamp, result;
const later = function () {
// 据上一次触发时间间隔
let last = Number(new Date()) - timestamp;
// 上次被包装函数被调用时间间隔last小于设定时间间隔wait
if (last < wait && last > 0) {
timeout = setTimeout(later, wait - last);
} else {
timeout = null;
// 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用
if (!immediate) {
result = func.call(context, ...args, context);
context = args = null;
}
}
};
return function (..._args) {
context = this;
args = _args;
timestamp = Number(new Date());
const callNow = immediate && !timeout;
// 如果延时不存在,重新设定延时
if (!timeout) {
timeout = setTimeout(later, wait);
}
if (callNow) {
result = func.call(context, ...args, context);
context = args = null;
}
return result;
};
};
/**
* 节流 返回函数连续调用时,func 执行频率限定为 次 / wait
*
* @param {function} func 传入函数
* @param {number} wait 表示时间窗口的间隔
* @param {object} options 如果想忽略开始边界上的调用,传入{leading: false}。
* 如果想忽略结尾边界上的调用,传入{trailing: false}
* @return {function} 返回客户调用函数
*/
export const throttle_fn = function (func, wait, options) {
let context, args, result;
let timeout = null;
// 上次执行时间点
let previous = 0;
if (!options) options = {};
// 延迟执行函数
let later = function () {
// 若设定了开始边界不执行选项,上次执行时间始终为0
previous = options.leading === false ? 0 : Number(new Date());
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
return function (..._args) {
let now = Number(new Date());
// 首次执行时,如果设定了开始边界不执行选项,将上次执行时间设定为当前时间。
if (!previous && options.leading === false) previous = now;
// 延迟执行时间间隔
let remaining = wait - (now - previous);
context = this;
args = _args;
// 延迟时间间隔remaining小于等于0,表示上次执行至此所间隔时间已经超过一个时间窗口
// remaining大于时间窗口wait,表示客户端系统时间被调整过
if (remaining <= 0 || remaining > wait) {
clearTimeout(timeout);
timeout = null;
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
//如果延迟执行不存在,且没有设定结尾边界不执行选项
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
};