0

I am writing a class that I want to include multiple widgets that can be displayed in a Jupyter notebook. These widgets should calls class methods that update class parameters. A function that I connect to an ipywidget's events need access to the class instance, I think through self, but I can't figure out how to get this communication to work.

Here's a minimal example:

import numpy as np
import ipywidgets as widgets

class Test(object):
    def __init__(self):
        self.val = np.random.rand()
        display(self._random_button)

    _random_button = widgets.Button(
        description='randomize self.val'
    )

    def update_random(self):
        self.val = np.random.rand()
        print(self.val)

    def button_pressed(self):
        self.update_random()

    _random_button.on_click(button_pressed)

I see how the button_pressed() function sees the Button instance as self, giving "AttributeError: 'Button' object has no attribute 'update_random'".

Is there a way that I can access methods of the class Test through a button that belongs to the class, or is there a better way that I should be structuring this code to ease communication between these components?

astrokeat
  • 78
  • 1
  • 7

2 Answers2

3
  1. The button widget and the on_click should be created (or initialised) in the init method.
  2. The on_click method generates an argument that is sent to the function, but it is not needed in this case so I have just put a *args in the button_pressed function.
  3. The display call is not needed.
  4. When calling a function in a class, you must use self.functionName. That includes the function calls in on_click or observe
  5. In this case, you didn't need the random number generation in the init function.

There are a few examples of Jupyter widgets within classes here: https://github.com/bloomberg/bqplot/tree/master/examples/Applications

import numpy as np
import ipywidgets as widgets

class Test(object):
    def __init__(self):
        self.random_button = widgets.Button(
            description='randomize self.val')
        self.random_button.on_click(self.button_pressed)

    def update_random(self):
        self.val = np.random.rand()
        print(self.val)

    def button_pressed(self,*args):
        self.update_random()

buttonObject = Test()
# display(buttonObject.random_button)  # display works but is not required if on the last line in Jupyter cell.
buttonObject.random_button  # Widget to be dispalyed - must last last line in cell
DougR
  • 3,196
  • 1
  • 28
  • 29
  • Thank you so much for clearing this up for me. This works as I wanted, and it has helped me to better understand classes. Thanks especially for pointing out other improvements to the code that weren't strictly needed to produce the intended behavior. – astrokeat Apr 22 '19 at 16:20
1

When using JupyterLab, if you want the output to show in the notebook cell, rather than in the notebook log, a minor tweak is needed to @DougR's excellent answer:

import numpy as np
import ipywidgets as widgets

# create an output widget
rand_num_output = widgets.Output()

class Test(object):
    def __init__(self):
        self.random_button = widgets.Button(
            description='randomize self.val')
        self.random_button.on_click(self.button_pressed)

    def update_random(self):
        # clear the output on every click of randomize self.val
        rand_num_output.clear_output()
        
        # execute function so it gets captured in output widget view
        with rand_num_output:
            self.val = np.random.rand()
            print(self.val)

    def button_pressed(self,*args):
        self.update_random()

buttonObject = Test()
# display(buttonObject.random_button)  # display works but is not required if on the last line in Jupyter cell.
widgets.HBox([buttonObject.random_button, rand_num_output])  # Widget to be dispalyed - must last last line in cell, add output widget
WildGoose
  • 1,981
  • 1
  • 5
  • 4