Search code examples
pythondropboxipywidgets

ipywidget observe method called several times for every change, why?


I am trying to learn how ipywidget dropdown menu works with an observe method and came across to this really useful SO question: ipywidgets dropdown widgets: what is the onchange event?

Good explanations can be found there.

Tweaking a bit some code of the answers I created this little script:

w = wd.Dropdown(
    options=['Addition', 'Multiplication', 'Subtraction'],
    value='Addition',
    description='Task:',
)

def on_change(change):
    print('method is called when printing this')
    if change['type'] == 'change' and change['name'] == 'value':
        print("changed to %s" % change.new)
    else:
        print('chage type is not change it is actually:', change['type'])
        print('chage name is not value it is actually:', change['name'])

w.observe(on_change)

display(w)

The weird thing is that when changing the value of the dropdown menu ONE TIME this is what is printed out:

method is called when printing this
chage type is not change it is actually: change
chage name is not value it is actually: _property_lock
method is called when printing this
chage type is not change it is actually: change
chage name is not value it is actually: label
method is called when printing this
changed to Multiplication
method is called when printing this
chage type is not change it is actually: change
chage name is not value it is actually: index
method is called when printing this
chage type is not change it is actually: change
chage name is not value it is actually: _property_lock

So the observe method is called 4 times for one single change of the dropdown of the menu.

Why is that?

secondly this does not happened when the observe is written like this:

w.observe(on_change, names='value')

Then the output is just:

method is called when printing this
changed to Multiplication

So in the second case the method is called one once.

Can someone explain what is going on here?


Solution

  • In

    w.observe(on_change)
    

    you're telling the widget to call the on_change function everytime any of its attribute (actually Trait attribute I guess), is changed: this includes the value (which is only what you want most of the time) but also all of its more "internal attributes" (like _property_lock, which you don't need most of the time). This behaviour is documented looking at the w.observe doc :

    Signature: w.observe(handler, names=traitlets.All, type='change')
    Docstring:
    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.
    

    the names defaults to traitlets.All and in this case : If names is All, the handler will apply to all traits.

    Hence the importance of the names=Value when defining you callback.