Как создать ipywidgets из нескольких?

Допустим, мне нужен виджет, состоящий из виджета IntText и виджета DropDown, значение которого представляет собой конкатенированную строку значений этих виджетов. Как я могу сделать?

Вот попытка:

import re
import ipywidgets as ipw

from IPython.display import display


class IntMultipliedDropdown:
    _VALUE_PATTERN = re.compile('(?P<num>\d+) (?P<option>\w+-?\w*)')

    def __init__(self, options, option_value, int_value=1):
        self.number = ipw.IntText(int_value)
        self.options = ipw.Dropdown(options=options, value=option_value)
        self.box = ipw.HBox([self.number, self.options])

        self.number.observe(self._on_changes, names='value')
        self.options.observe(self._on_changes, names='value')

        self._handelers = []

    def _on_changes(self, change):
        for handeler in self._handelers:
            handeler(self.value)

    @property
    def value(self):
        return "{} {}".format(self.number.value, self.options.value)

    @value.setter
    def value(self, value):
        match = re.search(self._VALUE_PATTERN, value)
        groupdict = match.groupdict()
        self.number.value = groupdict['num']
        self.options.value = groupdict['option']

    def _ipython_display_(self, **kwargs):
        return self.box._ipython_display_(**kwargs)

    def observe(self, handler):
        if handler not in self._handelers:
            self._handelers.append(handler)


mywidget = IntMultipliedDropdown(['apple', 'bed', 'cell'], 'cell')
mywidget.observe(print)

display(mywidget)
print('default value:', mywidget.value)

mywidget.value = '2 bed'

Работает, но есть недостатки. Во-первых, когда я устанавливаю mywidget.value, наблюдаемая функция вызывается два раза: при изменении значения числа и при изменении значения параметра.

Во-вторых, и самое худшее, я не могу использовать этот виджет в виджете Box, например:

ipw.HBox([ipw.Label('Mylabel'), mywidget])

Что повышает:

ValueError: Can't clean for JSON: <__main__.IntMultipliedDropdown object at 0x7f7d604fff28>

Есть ли лучшее решение?


person PhML    schedule 14.11.2016    source источник


Ответы (2)


  1. Созданный вами класс не является виджетом, хотя вы имитировали некоторые варианты поведения (observe, display). Вероятно, поэтому вы не смогли отобразить его в файле HBox. Если вы хотите создать новый виджет, наследуйте от ipyw.Widget или любого другого виджета.
  2. У вас есть два основных виджета, которые прослушиваются, поэтому нормально, что две функции вызываются при изменении их значений. Если вы хотите, чтобы вызывалась только одна функция, слушайте непосредственно value вашего нового виджета.

Вот как вы могли бы это сделать, наследуя от HBox:

import re
import ipywidgets as ipw
from traitlets import Unicode
from IPython.display import display


class IntMultipliedDropdown(ipw.HBox):
    _VALUE_PATTERN = re.compile('(?P<num>\d+) (?P<option>\w+-?\w*)')
    value = Unicode()

    def __init__(self, options, option_value, int_value=1, **kwargs):
        self.number = ipw.IntText(int_value)
        self.options = ipw.Dropdown(options=options, value=option_value)

        self._update_value()

        self.number.observe(self._update_value, names='value')
        self.options.observe(self._update_value, names='value')
        self.observe(self._update_children, names='value')

        super().__init__(children=[self.number, self.options], **kwargs)


    def _update_children(self, *args):
        match = re.search(self._VALUE_PATTERN, self.value)
        groupdict = match.groupdict()
        self.number.value = groupdict['num']
        self.options.value = groupdict['option']

    def _update_value(self, *args):
        self.value = "{} {}".format(self.number.value, self.options.value)

mywidget = IntMultipliedDropdown(['apple', 'bed', 'cell'], 'cell')
display(mywidget)
person rmenegaux    schedule 14.02.2017
comment
для работы: super(IntMultipliedDropdown, self) - person Rodrigo E. Principe; 16.05.2018
comment
Я пробовал без функции _update_value, и все работает нормально, что очень важно, так это функция _update_children, которая, кстати, была бы лучше: _update_children(self, change), как указано в документах (traitlets.readthedocs.io/en/stable/using_traitlets.html#observe) - person Rodrigo E. Principe; 16.05.2018

Вероятно, есть причина, по которой вы приложили все усилия для создания нового виджета, но почему бы не использовать функцию interactive?

Что-то типа:

import ipywidgets as ipw
from ipywidgets import *

w_number = ipw.IntText(value = 1)
w_options = ipw.Dropdown(options = ['apple', 'bed', 'cell'], value ='cell')

mywidget_value = ''

def display_value(number, options):
    mywidget_value = str(number)+' '+options
    #print(mywidget_value)
    return mywidget_value

w_box = interactive(display_value, number=w_number, options=w_options)

display(w_box)

Тогда у вас есть aBox, и вы можете адаптировать его макет. Вы также можете получить доступ к аргументам ключевого слова с помощью w_box.kwargs или к возвращаемому значению функции с помощью w_box.result, которое представляет собой объединенную строку двух виджетов, которые вы искали...

person Ely    schedule 15.11.2016