python实现防抖函数debounce,以及setTimeout定时器

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;
  };
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值