Interactivity Using Functions

Functions are a powerful tool in programming. Not only do they give you a way to reuse and parameterize code but they open the possibility of event driven programming. Event driven programming is when a program responds to changes (called events) when they occur. Examples of events are a mouse click or key stroke. Event driven programs are interactive because they’re always waiting for something to happen. In this lesson you’ll learn how to bind a function to a widget so that the function is called every time the widget changes.

Interactive Functions

An interactive function gets argument values from widgets and returns something to display. Consider the power_of function from the last lesson. The solution to that question is shown here in the example below:

def power_of(sig, power):
    """Compute sig to the power of power"""
    return sig ** power

Enter the function into the next cell and test it:

[ ]:

Choose the Right Widgets

The power_of function takes two floats. In order to give it proper input we should use widgets that match the type of input the function expects. In this case we need widgets that are constrained to floats (or are at least numeric). For the significand I’m going to use a FloatText and for the power I’m going to use a FloatSlider.

import ipywidgets
sig_widget = ipywidgets.FloatText(
    description='Significand:',
)
pow_widget = ipywidgets.FloatSlider(
    min=0,
    max=10.0,
    description='Power:',
)
display(sig_widget, pow_widget)

Copy the example code into the next cell to create the widgets.

[ ]:

Binding Widgets to a Function

Now let’s make the function interactive using the widgets we just created. The ipywidgets.interact function let’s you bind widget values to arguments in your own function. This code creates widgets that supply the input to our power_of function and displays the return value:

ipywidgets.interact(power_of, sig=sig_widget, power=pow_widget)

Try running the example:

[ ]:

You can find the complete documentation for how to use interact on the ipywidgets home page. In this class you’ll only need to know the basics.

Building User Interfaces

The ipywidgets.interactive function is easy and useful, but it’s limited. Sometimes you want more control of how a program displays its widgets and output. In the projects for this class I will somtimes build interfaces for you and ask you to make functions. This section describes how I do that.

Here’s a pre-built user interface that takes two numbers and displays the output of two functions:

[ ]:
from p4e.widgets import bind
from ipywidgets import HBox, VBox

a_widget = ipywidgets.IntSlider(description="a:", min=0, max=100)
b_widget = ipywidgets.IntSlider(description="b:", min=1, max=100)
mult_out = bind('multiply', {'a': a_widget, 'b': b_widget})
div_out = bind('divide', {'a': a_widget, 'b': b_widget})

display(VBox([
    HBox([a_widget, b_widget]),
    mult_out,
    div_out,
]))

Write the Functions

The functions are missing from the example so when you run the code hte output of multiply and divide are errors. To finish the exercise you have to create them. Here are the definitions:

The multiply Function

Write a function called multiply that takes two arguments, a and b. The function returns the product of a and b.

  • Name: multiply

  • Arguments:

    • a (int)

    • b (int)

  • Returns: The a times b.

The divide Function

Write a function called divide that takes two arguments, a and b. The function returns a divided by b.

  • Name: divide

  • Arguments:

    • a (int)

    • b (int)

  • Returns: The a divided by b.

Write your two funcitons in the next cell:

[ ]:

Widgets are Global

The interface widgets are global. That means that they can be accessed from inside of your function when you use the global keyword. Accessing widgets is necessary when the way one widget looks or acts depends on another widget. Here’s an example:

Suppose you had a UI for ordering at a coffee shop. You can order an item and select add-ons for the item. The valid add-ons depend on the item. For example, coffee might have half-and-half, soy and almond milk and a bagel might have butter, cream cheese and lox. You don’t want the user to be able to select lox for their coffee!

Here’s an example UI:

[ ]:
from p4e.widgets import bind
import ipywidgets

for_sale = {
    'Coffee': ['Half and Half', 'Soy Milk', 'Almond Milk'],
    'Bagel': ['Butter', 'Cream Cheese', 'Lox'],
}

item_widget = ipywidgets.Dropdown(description='Item:', options=for_sale.keys())
addon_widget = ipywidgets.RadioButtons(description='Add:')

bind('update_item', {'item': item_widget})
display(
    item_widget, addon_widget,
)

The update_item function gets called every time the user changes the item_widget. The function uses the data in the global for_sale variable to update the addon_widget.options. Neither of those variables are local to the function so the global keyword is used. Here’s the update_item function:

def update_item(item):
    """Update the options when the item value changes"""
    global for_sale
    global addon_widget
    addon_widget.options = for_sale[item]

Enter the code into the next cell and test it:

[ ]: