首页 > 解决方案 > How to save ipywidget output and link it to a button to run another function with saved inputs

问题描述

This is going to be a kind of long question so I'll try to break it down as much as I can so please bear with me. My idea is to create an jupyter notebook app using jupyter widgets. My app will get some information using an API but it needs some inputs first which are time and entity name that also have some metadata. Because I have different ipywidgets incorporated here (SelectMultiple,2 Date pickers widgets (start, end) and button that would finally will load the function to get the data from the database.)

Currently my widgets look like this:

enter image description here

I'm able to obtain multiple entities ('Wells') (with its own metadata and assign an start and end date and save it in a dictionary). I have created a button but I'm having a hard time to be able to link the button to the already saved data to run my function everytime I run click in 'run'. Here's my code:

#I created a class to add multiple widgets
class SelectMutipleInteract(widgets.HBox):
        
    def __init__(self,api_obj):
        #In my init function I'm obtaining all the entities to be stored in the Selectmultiple widget
        env='prod'
        pre_df = api_obj.search_sites_by_name('',env).set_index('site_id').sort_index(ascending=True)
        pre_df = pre_df[pre_df['db'].str.contains('jon')]
        self.pre_df = pre_df
        
        self.w = widgets.SelectMultiple(
            options = pre_df.name.values,
            rows = len(pre_df),
            description = 'Wells',
            disabled = False)
        #From that same data I get initial start and end dates (which I'll be able to change based on the time I want)
        try:
            self.start_date = datetime.strptime(self.pre_df.sort_values(by='date_created')['date_created'].values[0],'%Y-%m-%dT%H:%M:%SZ')
            self.start_date = pd.Series(self.start_date).dt.round('H')[0]
        except:
            'TypeError'
        
        try:
            self.start_date = datetime.strptime(self.pre_df.sort_values(by='date_created')['date_created'].values[0],'%Y-%m-%dT%H:%M:%S.%fZ') 
            self.start_date = pd.Series(self.start_date).dt.round('H')[0]
        except:
            'TypeError'
        
        try:
            self.end_date = datetime.strptime(self.pre_df.sort_values(by='last_transmission',ascending=False)['last_transmission'].values[0],'%Y-%m-%dT%H:%M:%SZ')
            self.end_date = pd.Series(self.end_date).dt.round('H')[0]
        except:
            'TypeError'
            
        try:
            self.end_date = datetime.strptime(self.pre_df.sort_values(by='last_transmission',ascending=False)['last_transmission'].values[0],'%Y-%m-%dT%H:%M:%S.%fZ')
            self.end_date = pd.Series(self.end_date).dt.round('H')[0]
        except:
            'TypeError'

        # I created the rest of the widgets
        self.w1 = widgets.DatePicker(
                    description='Pick Start Date',
                    value = self.start_date)
        
        self.w2 = widgets.DatePicker(
                    description='Pick End Date',
                    value=self.end_date)
        
        self.w3 = widgets.Button(
                    description='Run',
                    button_style='info')
        
        self.selectors = [self.w,self.w1,self.w2,self.w3]
        
        #I think this as a super class and then each object to be assigned together
        super().__init__(children=self.selectors)
        self._set_observes()
        
    def _set_observes(self):
        self.selectors1 = self.selectors[:3]
        for widg in self.selectors1:
            widg.observe(self._observed_function_widgets1, names='value')
            
    #Observed function will work as the place to store the metadata        
    def _observed_function_widgets1(self,widg):
        self.dict = {}
        self.list_a = []
        
        for widg in self.selectors1:
            if type(widg.get_interact_value())==tuple:
                self.list_a.append(widg.get_interact_value())
            else:
                self.list_a.append(datetime.strftime(widg.get_interact_value(), '%Y-%m-%d %H:%M:%S'))
                
        for well in self.list_a[0]:
            a = self.pre_df.loc[self.pre_df['name']==well,:]
            self.dict[well]= {'domain':a['db'],'site_slug':a['slug'],'start_date':self.list_a[1],
                                'end_date':self.list_a[2]}
        
        print(self.dict)
        
        #This is where I put the connection of the button but I'm getting an error
        self.w3.on_click(self._on_button_clicked(self.dict))
        
        
    def _on_button_clicked(self,dict1):
        #Here I should run the final function
        print(1)
        print(dict1)
        
        
SelectMutipleInteract(api_obj)

The error that I get when I press the button (after I already selected entities and times) is

{'SHB 67-34': {'domain': site_id
183    jonah-energy
Name: db, dtype: object, 'site_slug': site_id
183    10004-24
Name: slug, dtype: object, 'start_date': '2019-09-26 15:00:00', 'end_date': '2020-12-17 21:00:00'}, 'SHB 75-34': {'domain': site_id
184    jonah-energy
Name: db, dtype: object, 'site_slug': site_id
184    10005-40
Name: slug, dtype: object, 'start_date': '2019-09-26 15:00:00', 'end_date': '2020-12-17 21:00:00'}, 'SHB 77-34': {'domain': site_id
185    jonah-energy
Name: db, dtype: object, 'site_slug': site_id
185    10006-46
Name: slug, dtype: object, 'start_date': '2019-09-26 15:00:00', 'end_date': '2020-12-17 21:00:00'}}
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
TypeError: 'NoneType' object is not callable

The whole idea is to (once the data has been selected and stored) then run the final function but I'm having a hard time conceptualize the creation on the button give that you need to run the several widgets first to get the data and store it and then click the button to run the function with that data they way I'm creating the widgets layout together.

Any help would really appreciate it.

标签: pythonjupyter-notebookwidgetipywidgets

解决方案


这里的问题真的很微妙。

您需要将可调用对象 self._on_button_clicked传递给self.w3.on_click. 您当前的代码调用该self._on_button_clicked函数,并传递该函数的返回值,该值确实为 None。然后,当您单击按钮时,on_click尝试调用 None,因此您的错误。

试着想想这段代码:

value = self._on_button_clicked(self.dict)
self.w3.on_click(value)

这与当前代码的实现完全相同,但希望它能说明为什么您所拥有的代码不起作用。value是无,然后您告诉按钮在单击它时调用无。

为避免这种情况,请进行以下更改:

  1. self.w3.on_click(self._on_button_clicked) # 传递函数本身,而不是调用该函数的结果。

  2. 更改观察到的函数代码以获取正文中所需的变量。

    def _on_button_clicked(self, button):  # the button arg gets passed, but you don't need it.
        #Here I should run the final function
        print(1)
        print(self.dict) # grab the instance variables you want

在大多数情况下,最好在观察到的函数中获取所需的变量,而不是在进行赋值时尝试传递它们on_click。如果您真的无法在函数中获取所需的变量(例如,变量超出范围),那么您可以使用 functools.partial。


推荐阅读