5

Assume I have this simple function in Python:

def f(gender, name):
    if gender == 'male':
        return ranking_male(name)
    else:
        return ranking_female(name)

where gender belongs to ['male', 'female'] whereas name belongs to ['Adam', 'John', 'Max', 'Frodo'] (if gender is male) or ['Mary', 'Sarah', 'Arwen'] (otherwise).

I wish to apply interact from ipywidgets to this function f. Normally one would do

from ipywidgets import interact
interact(f, gender = ('male', 'female'), name = ('Adam', 'John', 'Max', 'Frodo'))

The problem is that the admissible values for name now depend on the value chosen for gender.

I tried to find it in the docs but couldn't find it. The only thing I think may be important is 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.

But I have no idea how to do it nor to interpret what the doc string is talking about. Any help is much appreciated!

gota
  • 2,338
  • 4
  • 25
  • 45

2 Answers2

2

For example you have brand and model of car and model depends on brand.

d = {'Volkswagen' : ['Tiguan', 'Passat', 'Polo', 'Touareg', 'Jetta'], 'Chevrolet' : ['TAHOE', 'CAMARO'] }

brand_widget = Dropdown( options=list(d.keys()),
                         value='Volkswagen',
                         description='Brand:',
                         style=style
                       )
model_widget = Dropdown( options=d['Volkswagen'],
                         value=None,
                         description='Model:',
                         style=style
                       )

def on_update_brand_widget(*args):
    model_widget.options = d[brand_widget.value]

brand_widget.observe(on_update_brand_widget, 'value')
mrgloom
  • 20,061
  • 36
  • 171
  • 301
  • very interesting! Thanks! can you explain what this line is doing? `brand_widget.observe(on_update_brand_widget, 'value')` – gota Feb 27 '18 at 13:24
  • Callback registration : http://ipywidgets.readthedocs.io/en/latest/examples/Widget%20Events.html#Traitlet-events – mrgloom Feb 27 '18 at 13:32
1

I've used nested widgets to solve this problem. It'll work, but it's ugly, partially because it doesn't seem to be a common use case in ipywidgets (see discussion).

Given your function f(gender, name) you can define an intermediate wrapper:

def f_intermediate_wrapper(gender):
    if gender=="male":
        possible_names = ['Adam', 'John', 'Max', 'Frodo']
    else:
        possible_names = ['Mary', 'Sarah', 'Arwen']

    try:
        f_intermediate_wrapper.name_widget.widget.close()
    except AttributeError:
        pass

    f_intermediate_wrapper.name_widget = interact(f,
                                                  gender=widgets.fixed(gender),
                                                  name = possible_names)   

The first part sets the possible name options given the gender, as desired.

The second part closes the name_widget from your previous evaluation, if it exists. Otherwise, every time you change the gender, it'll leave up the old list of names, which are the wrong gender (see example).

The third part creates a name widget of the possible names for that gender, and stores it somewhere sufficiently static. (Otherwise, when you change the gender the old name widget will be out of scope, and you won't be able to close it.)

Now you can create your gender and name widget:

gender_and_name_widget = interact(f_intermediate_wrapper,
                                  gender = ["male", "female"])

And you can access the result of your f(gender, name) using

gender_and_name_widget.name_widget.widget.result
Eric G.
  • 592
  • 3
  • 6