Button is not used to represent a data type. Instead the button widget is used to handle mouse clicks. The
on_click method of the
Button can be used to register function to be called when the button is clicked. The doc string of the
on_click can be seen below.
import ipywidgets as widgets print(widgets.Button.on_click.__doc__)
Register a callback to execute when the button is clicked. The callback will be called with one argument, the clicked button widget instance. Parameters ---------- remove: bool (optional) Set to true to remove the callback from the list of callbacks.
Since button clicks are stateless, they are transmitted from the front-end to the back-end using custom messages. By using the
on_click method, a button that prints a message when it has been clicked is shown below. To capture
Output widget (or put the information you want to display into an
from IPython.display import display button = widgets.Button(description="Click Me!") output = widgets.Output() display(button, output) def on_button_clicked(b): with output: print("Button clicked.") button.on_click(on_button_clicked)
Widget properties are IPython traitlets and traitlets are eventful. To handle changes, the
observe method of the widget can be used to register a callback. The doc string for
observe can be seen below.
Setup a handler to be called when a trait changes. This is used to setup dynamic notifications of trait changes. Parameters ---------- handler : callable A callable that is called when a trait changes. Its signature should be ``handler(change)``, where ``change`` is a dictionary. The change dictionary at least holds a 'type' key. * ``type``: the type of notification. Other keys may be passed depending on the value of 'type'. In the case where type is 'change', we also have the following keys: * ``owner`` : the HasTraits instance * ``old`` : the old value of the modified trait attribute * ``new`` : the new value of the modified trait attribute * ``name`` : the name of the modified trait attribute. names : list, str, All If names is All, the handler will apply to all traits. If a list of str, handler will apply to all names in the list. If a str, the handler will apply just to that name. type : str, All (default: 'change') The type of notification to filter by. If equal to All, then all notifications are passed to the observe handler.
Mentioned in the doc string, the callback registered must have the signature
change is a dictionary holding the information about the change.
Using this method, an example of how to output an
IntSlider’s value as it is changed can be seen below.
int_range = widgets.IntSlider() output2 = widgets.Output() display(int_range, output2) def on_value_change(change): with output2: print(change['new']) int_range.observe(on_value_change, names='value')
Some widgets offer a choice with their
continuous_update attribute between continually updating values or only updating values when a user submits the value (for example, by pressing Enter or navigating away from the control). In the next example, we see the “Delayed” controls only transmit their value after the user finishes dragging the slider or submitting the textbox. The “Continuous” controls continually transmit their values as they are changed. Try typing a two-digit number into each of the text boxes, or dragging each of the sliders, to see the difference.
a = widgets.IntSlider(description="Delayed", continuous_update=False) b = widgets.IntText(description="Delayed", continuous_update=False) c = widgets.IntSlider(description="Continuous", continuous_update=True) d = widgets.IntText(description="Continuous", continuous_update=True) widgets.link((a, 'value'), (b, 'value')) widgets.link((a, 'value'), (c, 'value')) widgets.link((a, 'value'), (d, 'value')) widgets.VBox([a,b,c,d])
Textarea controls default to
IntText and other text boxes for entering integer or float numbers default to
continuous_update=False (since often you’ll want to type an entire number before submitting the value by pressing enter or navigating out of the box).
When trait changes trigger a callback that performs a heavy computation, you may want to not do the computation as often as the value is updated. For instance, if the trait is driven by a slider which has its
continuous_update set to
True, the user will trigger a bunch of computations in rapid succession.
Debouncing solves this problem by delaying callback execution until the value has not changed for a certain time, after which the callback is called with the latest value. The effect is that the callback is only called when the trait pauses changing for a certain amount of time.
Debouncing can be implemented using an asynchronous loop or threads. We show an asynchronous solution below, which is more suited for ipywidgets. If you would like to instead use threads to do the debouncing, replace the
Timer class with
from threading import Timer.
import asyncio class Timer: def __init__(self, timeout, callback): self._timeout = timeout self._callback = callback async def _job(self): await asyncio.sleep(self._timeout) self._callback() def start(self): self._task = asyncio.ensure_future(self._job()) def cancel(self): self._task.cancel() def debounce(wait): """ Decorator that will postpone a function's execution until after `wait` seconds have elapsed since the last time it was invoked. """ def decorator(fn): timer = None def debounced(*args, **kwargs): nonlocal timer def call_it(): fn(*args, **kwargs) if timer is not None: timer.cancel() timer = Timer(wait, call_it) timer.start() return debounced return decorator
Here is how we use the
debounce function as a decorator. Try changing the value of the slider. The text box will only update after the slider has paused for about 0.2 seconds.
slider = widgets.IntSlider() text = widgets.IntText() @debounce(0.2) def value_changed(change): text.value = change.new slider.observe(value_changed, 'value') widgets.VBox([slider, text])
Throttling is another technique that can be used to limit callbacks. Whereas debouncing ignores calls to a function if a certain amount of time has not passed since the last (attempt of) call to the function, throttling will just limit the rate of calls. This ensures that the function is regularly called.
We show an synchronous solution below. Likewise, you can replace the
Timer class with
from threading import Timer if you want to use threads instead of asynchronous programming.
import asyncio from time import time def throttle(wait): """ Decorator that prevents a function from being called more than once every wait period. """ def decorator(fn): time_of_last_call = 0 scheduled, timer = False, None new_args, new_kwargs = None, None def throttled(*args, **kwargs): nonlocal new_args, new_kwargs, time_of_last_call, scheduled, timer def call_it(): nonlocal new_args, new_kwargs, time_of_last_call, scheduled, timer time_of_last_call = time() fn(*new_args, **new_kwargs) scheduled = False time_since_last_call = time() - time_of_last_call new_args, new_kwargs = args, kwargs if not scheduled: scheduled = True new_wait = max(0, wait - time_since_last_call) timer = Timer(new_wait, call_it) timer.start() return throttled return decorator
To see how different it behaves compared to the debouncer, here is the same slider example with its throttled value displayed in the text box. Notice how much more interactive it is, while still limiting the callback rate.
slider = widgets.IntSlider() text = widgets.IntText() @throttle(0.2) def value_changed(change): text.value = change.new slider.observe(value_changed, 'value') widgets.VBox([slider, text])