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?
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.