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

Viewed 528

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.

2 Answers

You can also unobserve a function when you change the value, and then reset it later.

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

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.

Related