0

I am writing a function to allow the user the select from a series of options and then return values based on these selections. I am using Jupyter Widgets for the selection and running in JupyterLab. My selection function works fine on its own, but once it has been embedded in another function, it stops to working. Example:

import ipywidgets as widgets

def get_choices():    
    selections = [
    widgets.ToggleButtons(
        options=['Not Included', 'Included', 'Favorite'],
        description=f"{choice}",
        disabled=False,
        style= {'description_width': '300px'}
    )
    for choice in ['choice1', 'choice2', 'choice3']
   ]
    
    for e in selections:
        display(e)

    ## waiting for user input
    print("\n\nPRESS ENTER WHEN FINISHED")
    input()
    
    return wiki_edges_select

choices = get_choices()

print(choices)
>> [ToggleButtons(description='choice1', index=1, options=('Not Included', 'Included', 'Favorite'), style=ToggleButtonsStyle(description_width='300px'), value='Included'),
 ToggleButtons(description='choice2', index=1, options=('Not Included', 'Included', 'Favorite'), style=ToggleButtonsStyle(description_width='300px'), value='Included'),
 ToggleButtons(description='choice3', index=2, options=('Not Included', 'Included', 'Favorite'), style=ToggleButtonsStyle(description_width='300px'), value='Favorite')]

(Note that the values are Included, Included, Favorite). However, when embedded in a wrapper function:

def get_choices_and_process():
    choices = get_choices()
    print(choices)

get_choices_and_process()
>> [ToggleButtons(description='choice1', options=('Not Included', 'Included', 'Favorite'), style=ToggleButtonsStyle(description_width='300px'), value='Not Included'), ToggleButtons(description='choice2', options=('Not Included', 'Included', 'Favorite'), style=ToggleButtonsStyle(description_width='300px'), value='Not Included'), ToggleButtons(description='choice3', options=('Not Included', 'Included', 'Favorite'), style=ToggleButtonsStyle(description_width='300px'), value='Not Included')]

(Note that the values are Not Included, Not Included, Not Included)

I would like to have the choices returned within the get_choices_and_process() function reflect the user's selections as they do when get_choices() is called outside of the wrapper. How can I make this work?

Fortunato
  • 567
  • 6
  • 18

2 Answers2

0

I wasn't able to do it exactly has posted, but this solution proved to be good enough. Leaving open, as this does not exactly answer the question.

%gui asyncio
import asyncio
import ipywidgets as widgets

def get_choices():    
    selections = [
    widgets.ToggleButtons(
        options=['Not Included', 'Included', 'Favorite'],
        description=f"{choice}",
        disabled=False,
        style= {'description_width': '300px'}
    )
    for choice in ['choice1', 'choice2', 'choice3']
   ]
    
    for e in selections:
        display(e)

    return wiki_edges_select

def process_choices(choices):
    included_choices, favorite_choices = [], []
    for choice in choices:
        if choice.value == "Included":
           included_choices.append(choice)
        if choice.value == "Favorite":
           favorite_choices.append(choice)
    return included_choices, favorite_choices

def wait_for_change(widget):
    """
    This function found here: https://stackoverflow.com/questions/55244865/pause-jupyter-notebook-widgets-waiting-for-user-input
    """
    future = asyncio.Future()
    def getvalue(change):
        future.set_result(change.description)
        widget.on_click(getvalue, remove=True) 
    widget.on_click(getvalue)
    return future

async def get_choices_and_process():
    choices = get_choices()
    
    ## Waiting for selections
    button = widgets.Button(description="Click to Continue")
    display(button)
    x = await wait_for_change(button)

    included_choices, favorite_choices  = process_choices(choices)

    return my_results

processed_choices = asyncio.create_task(get_choices_and_process() )

processed_choices.result()
>> ["choice1", "choice2"], ["choice3"]

Also - this only works on Jupyter-notebook, not Jupyter-lab, which is fine for my application

Fortunato
  • 567
  • 6
  • 18
  • Usually, it not working in JupyterLab means you aren't using conventions and approaches consistent with the current usage of ipywidgets. There's a lot of old code out there that soon will cease to work when the machinery underlying classic notebook interface are updated to be like JupyterLab is using, see [here](https://github.com/jupyter/notebook#notebook-v7). Specifically, I note your code doesn't addressing handling output. This is key to current widget approaches. – Wayne Sep 23 '22 at 18:49
0

Unless I am misunderstanding what you are trying to do, you can use ipywidgets interactive function to let ipywidgets handle setting up the UI and making it interactive. As well as maintaining that interactivity. You can access what the settings are as they update using the keyword arguments assigned to the interactive function instance. For example, if you assign the instance to w, w.kwargs will give you the current values.

It works embedded in another function as I can show with a demonstration. (This was worked out in JupyterLab.)

Example for the settings

Put the following in a JupyterLab cell:

#based on https://stackoverflow.com/q/65137656/8508004 and https://ipywidgets.readthedocs.io/en/stable/examples/Using%20Interact.html?highlight=interactive#interactive
from ipywidgets import interactive, ToggleButtons

def print_choices(choice1, choice2, choice3):
    print(choice1)
    print(choice2)
    print(choice3)

choice1Select = ToggleButtons(options=['Not Included', 'Included', 'Favorite'],description='Choice1:',disabled=False,style= {'description_width': '300px'})
choice2Select = ToggleButtons(options=['Not Included', 'Included', 'Favorite'],description='Choice2:',disabled=False,style= {'description_width': '300px'})
choice3Select = ToggleButtons(options=['Not Included', 'Included', 'Favorite'],description='Choice3:',disabled=False,style= {'description_width': '300px'})

w = interactive(print_choices, choice1=choice1Select, choice2=choice2Select, choice3=choice3Select)
w

You can select the settings and see the updates below.

Example embedded in the wrapper function

For the wrapper function, try this in another cell below that one:

def get_choices_and_process(choices):
    for tag,choice in choices.items():
        print(f"{tag} is {choice}.")
    #print(f"choices are:{choices}")

get_choices_and_process(w.kwargs)

Now go up and change around the choices in the previous cell. And then run this cell again.


Alternate option with list comprehension defining the ToggleButtons more in line with the OP:
#based on https://stackoverflow.com/q/65137656/8508004 and https://ipywidgets.readthedocs.io/en/stable/examples/Using%20Interact.html?highlight=interactive#interactive
from ipywidgets import interactive, ToggleButtons

def print_choices(choice1,choice2, choice3):
    print(choice1)
    print(choice2)
    print(choice3)

choice1Select, choice2Select, choice3Select = [
    ToggleButtons(
        options=['Not Included', 'Included', 'Favorite'],
        description=f"{choice}",
        disabled=False,
        style= {'description_width': '300px'}
    )
    for choice in ['choice1', 'choice2', 'choice3']
   ]

w = interactive(print_choices, choice1=choice1Select, choice2=choice2Select, choice3=choice3Select)
w

Asyncio may be the way to go if interactive() not useable

It may be useful to point out this solution to Is it possible to get the current value of a widget slider from a function without using multithreading? because use of asyncio there allows to keep getting updated information from a widget that selects a value while allowing use of time.sleep() which seems to block interactive() from working.

I haven't working out how to adapt that to the OP quite yet.

Wayne
  • 6,607
  • 8
  • 36
  • 93