Search code examples
pythonjupyterinfinite-looponchangeipywidgets

How to prevent infinite loop with two widgets that update each other?


I'm experimenting with Jupyter Widgets to see if I can make a better version of this, but apparently the observe handler triggers even when the change is not from the user, so it creates an infinite loop of one widget changing the other. Minimal example:

import ipywidgets as widgets

a = widgets.FloatText(description='a:')
b = widgets.FloatText(description='b:')


def on_a_change(change):
    b.value = change['new'] + 1
    
a.observe(on_a_change, names='value')


def on_b_change(change):
    a.value = change['new'] + 1

b.observe(on_b_change, names='value')


display(a)
display(b)

Is there any way to only trigger on a user-initiated change? In other words, the user updates one text box, and then the others are updated from it, but those updates don't trigger more updates.


Solution

  • Not sure if I understood the requirement, but if it is "update one more than other", then you can try the following

    import ipywidgets as widgets
    
    a = widgets.FloatText(description='a:')
    b = widgets.FloatText(description='b:')
    
    def update_one(x):
        return x + 1
    
    widgets.link((a, 'value'), (b, 'value'), (update_one, update_one))
    
    display(a)
    display(b)
    

    widgets.link seems to do the trick, if you do widgets.link.__doc__ you can see the docs and it accepts a third parameter.

    >>> print(widgets.link.__doc__)
    Link traits from different objects together so they remain in sync.
    
        Parameters
        ----------
        source : (object / attribute name) pair
        target : (object / attribute name) pair
        transform: iterable with two callables (optional)
            Data transformation between source and target and target and source.
    

    So I have added an iterable of callables and made it return 1 more than your entered value.