Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A debounce function decorator in Python similar to the one in underscore.js, tested with 2.7
from threading import Timer
def debounce(wait):
""" Decorator that will postpone a functions
execution until after wait seconds
have elapsed since the last time it was invoked. """
def decorator(fn):
def debounced(*args, **kwargs):
def call_it():
fn(*args, **kwargs)
try:
debounced.t.cancel()
except(AttributeError):
pass
debounced.t = Timer(wait, call_it)
debounced.t.start()
return debounced
return decorator
import unittest
import time
from debounce import debounce
class TestDebounce(unittest.TestCase):
@debounce(10)
def increment(self):
""" Simple function that
increments a counter when
called, used to test the
debounce function decorator """
self.count += 1
def setUp(self):
self.count = 0
def test_debounce(self):
""" Test that the increment
function is being debounced.
The counter should only be incremented
once 10 seconds after the last call
to the function """
self.assertTrue(self.count == 0)
self.increment()
self.increment()
time.sleep(9)
self.assertTrue(self.count == 0)
self.increment()
self.increment()
self.increment()
self.increment()
self.assertTrue(self.count == 0)
time.sleep(10)
self.assertTrue(self.count == 1)
if __name__ == '__main__':
unittest.main()
@vladignatyev

This comment has been minimized.

Copy link

vladignatyev commented May 8, 2014

Look at https://github.com/vladignatyev/limiter
It is like debounce but it doesn't use timer and locks current thread.
Your implementation is similar to this one from JS LoDash framework: https://github.com/lodash/lodash/blob/master/dist/lodash.js#L4975
Have a nice day!

@esromneb

This comment has been minimized.

Copy link

esromneb commented Nov 12, 2015

Hey @vladignatyev The first link you mentioned all calls sleep() A proper debounce should never call sleep. I just wrote a better one here:
https://gist.github.com/esromneb/8eac6bf5bdfef58304cb

@Saberos

This comment has been minimized.

Copy link

Saberos commented Nov 16, 2015

Actually, @esromneb, your implementation behaves as the throttle function of underscore.js. The difference being your implementation triggers on the first call, and blocks any calls made in the specified period afterwards. The debounce function of underscore.js however, waits the specified amount of time after the first call, before actually executing it. If in that waiting period another call is made, the timer is reset. I can't think of an easy way to achieve this without sleep.

Whether this is how a debounce function works in an electrical system I do not know, but which functionality is preferred depends on the use case. In my scenario, I wish to process a file after it has stopped being changed for a while, thus the underscore.js version is prefered.

@hathawsh

This comment has been minimized.

Copy link

hathawsh commented Nov 7, 2017

FWIW, here's a version of the debounce decorator that works in Twisted Python.

from twisted.internet import reactor

def debounce(wait):
    def decorator(fn):
        def debounced(*args, **kw):
            try:
                debounced.delayed_call
            except AttributeError:
                def call_it():
                    del debounced.delayed_call
                    fn(*args, **kw)
                debounced.delayed_call = reactor.callLater(wait, call_it)
            else:
                debounced.delayed_call.reset(wait)
        return debounced
    return decorator
@kylebebak

This comment has been minimized.

Copy link

kylebebak commented Dec 9, 2017

Here's another way. This uses Python 3's nonlocal keyword. The implementation is direct and very easy to understand.

import time


def debounce(s):
    """Decorator ensures function that can only be called once every `s` seconds.
    """
    def decorate(f):
        t = None

        def wrapped(*args, **kwargs):
            nonlocal t
            t_ = time.time()
            if t is None or t_ - t >= s:
                result = f(*args, **kwargs)
                t = time.time()
                return result
        return wrapped
    return decorate

Try it out.

@debounce(3)
def hi(name):
    print('hi {}'.format(name))


hi('dude')
time.sleep(1)
hi('mike')
time.sleep(1)
hi('mary')
time.sleep(1)
hi('jane')
@neekey

This comment has been minimized.

Copy link

neekey commented May 3, 2018

@kylebebak your version is not "bounce" but as your description

ensures function that can only be called once every s seconds.

you version will always invoke the first call, and all the calls afterwards before the "s" seconds will be omitted

@MrSpider

This comment has been minimized.

Copy link

MrSpider commented Feb 21, 2019

def debounce(wait):
    """ Decorator that will postpone a functions
        execution until after wait seconds
        have elapsed since the last time it was invoked. """

    def decorator(fn):
        def debounced(*args, **kwargs):
            def call_it():
                debounced._timer = None
                debounced._last_call = time.time()
                return fn(*args, **kwargs)

            time_since_last_call = time.time() - debounced._last_call
            if time_since_last_call >= wait:
                return call_it()

            if debounced._timer is None:
                debounced._timer = threading.Timer(wait - time_since_last_call, call_it)
                debounced._timer.start()

        debounced._timer = None
        debounced._last_call = 0

        return debounced

    return decorator

This debounce decorator will directly call the function if the last call is more than wait seconds ago. Otherwise it starts a timer to call the function after wait seconds. This means if the decorated function is called once every second, the first call is passed through and the second call (which happens 1s after the first call) is scheduled to be executed after 14s. Calls that happen after a call has been scheduled will be ignored. One could also change the decorator so that always the last given arguments are used for the next scheduled call.

@kylebebak

This comment has been minimized.

Copy link

kylebebak commented Jul 12, 2019

@kylebebak your version is not "bounce" but as your description

ensures function that can only be called once every s seconds.

you version will always invoke the first call, and all the calls afterwards before the "s" seconds will be omitted

@neekey
Thanks for mentioning this, you're totally right! I think what I've written is typically called throttle, but I'm not sure this is the right name either.

@RafayAK

This comment has been minimized.

Copy link

RafayAK commented Dec 22, 2019

@kylebebak here is a great visualization for understanding throttle vs debounce:
http://demo.nimius.net/debounce_throttle/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.