如何在 ipywidgets 中分离模型和视图?
How do I separate model and view in ipywidgets?
考虑一个简单的外汇计算器应用程序示例。
我可以使用 traitlets
:
定义我的模型
from traitlets import HasTraits, Float, observe, Enum
import math
class FXModel(HasTraits):
domestic_qty = Float()
foreign_qty = Float()
fx_rate = Float(float('nan')) # in units of domestic_qty/foreign_qty
lock = Enum(['domestic', 'foreign'], default_value='domestic')
_calculating = Enum([None, 'domestic', 'foreign'], default_value=None)
def calc_foreign(self):
if not math.isnan(self.fx_rate):
self._calculating = 'foreign'
self.foreign_qty = self.domestic_qty / self.fx_rate
self._calculating = None
def calc_domestic(self):
if not math.isnan(self.fx_rate):
self._calculating = 'domestic'
self.domestic_qty = self.foreign_qty * self.fx_rate
self._calculating = None
@observe('domestic_qty')
def on_domestic(self, change):
if self._calculating is None:
self.calc_foreign()
@observe('foreign_qty')
def on_foreign(self, change):
if self._calculating is None:
self.calc_domestic()
@observe('fx_rate')
def on_fxrate(self, change):
if self.lock == 'domestic':
self.calc_foreign()
else:
self.calc_domestic()
以及相应的基于 "print" 的简单视图:
class FXView:
def __init__(self, model):
self.model = model
def show(self):
print("""
domestic_qty: {:.4g}
foreign_qty: {:.4g}
fx_rate: {:.4g}
lock: {}""".format(
self.model.domestic_qty,
self.model.foreign_qty,
self.model.fx_rate,
self.model.lock
))
工作原理如下:
>> fx_model = FXModel(domestic_qty = 100., fx_rate = 200.)
>> fx_view = FXView(fx_model)
>> fx_view.show()
domestic_qty: 100
foreign_qty: 0.5
fx_rate: 200
lock: domestic
>> fx_model.fx_rate = 195.
>> fx_view.show()
domestic_qty: 100
foreign_qty: 0.5128
fx_rate: 195
lock: domestic
我还使用 ipywidgets 创建了一个视图:
import ipywidgets as widgets
domestic_label = widgets.Label("Domestic quantity")
domestic_field = widgets.FloatText()
foreign_label = widgets.Label("Foreign quantity")
foreign_field = widgets.FloatText()
fx_label = widgets.Label("Exchange rate (domestic/foreign)")
fx_field = widgets.FloatText()
lock_label = widgets.Label("If rates change, keep ")
lock_field = widgets.Dropdown(options=["domestic", "foreign"])
lock_label_post = widgets.Label('fixed')
ipyview = widgets.HBox([widgets.VBox([domestic_label, foreign_label, fx_label, lock_label]),
widgets.VBox([domestic_field, foreign_field, fx_field, widgets.HBox([lock_field, lock_label_post])])])
看起来真不错:
我的问题是;我如何 "bind" 我的模型和我的 ipyview 在一起?我对 enaml
有一些经验,这可以通过操作员 :=
和朋友实现。
使用 ipywidgets
执行此操作的最佳方法是什么?
好问题!我 post 在 ipywidgets 问题中编辑了一个答案:https://github.com/jupyter-widgets/ipywidgets/issues/2296
下面是如何使用 post 中的小部件使用 ipywidget 视图制作自定义视图。关键是 link 调用以将属性绑定到小部件值。
import ipywidgets as widgets
from traitlets import link
from IPython.display import display
class FXWidgetView:
def __init__(self, model):
self.model = model
self.domestic_label = widgets.Label("Domestic quantity")
self.domestic_field = widgets.FloatText()
self.foreign_label = widgets.Label("Foreign quantity")
self.foreign_field = widgets.FloatText()
self.fx_label = widgets.Label("Exchange rate (domestic/foreign)")
self.fx_field = widgets.FloatText()
self.lock_label = widgets.Label("If rates change, keep ")
self.lock_field = widgets.Dropdown(options=["domestic", "foreign"])
self.lock_label_post = widgets.Label('fixed')
self.ipyview = widgets.HBox([widgets.VBox([self.domestic_label, self.foreign_label, self.fx_label, self.lock_label]),
widgets.VBox([self.domestic_field, self.foreign_field, self.fx_field, widgets.HBox([self.lock_field, self.lock_label_post])])])
link((model, 'domestic_qty'), (self.domestic_field, 'value'))
link((model, 'foreign_qty'), (self.foreign_field, 'value'))
link((model, 'fx_rate'), (self.fx_field, 'value'))
link((model, 'lock'), (self.lock_field, 'value'))
def _ipython_display_(self):
display(self.ipyview)
我要指出,Param 提倡这种分离有一段时间了,@jbednar 指出 ipywidgets 也可以实现便利功能来支持这种模式。我认为这是一个好主意 - 拥有一些简单的便利功能,这些功能比采用 HasTraits class 的交互功能更进一步,对其进行内省,并为常见情况的不同特征提供默认小部件。
考虑一个简单的外汇计算器应用程序示例。
我可以使用 traitlets
:
from traitlets import HasTraits, Float, observe, Enum
import math
class FXModel(HasTraits):
domestic_qty = Float()
foreign_qty = Float()
fx_rate = Float(float('nan')) # in units of domestic_qty/foreign_qty
lock = Enum(['domestic', 'foreign'], default_value='domestic')
_calculating = Enum([None, 'domestic', 'foreign'], default_value=None)
def calc_foreign(self):
if not math.isnan(self.fx_rate):
self._calculating = 'foreign'
self.foreign_qty = self.domestic_qty / self.fx_rate
self._calculating = None
def calc_domestic(self):
if not math.isnan(self.fx_rate):
self._calculating = 'domestic'
self.domestic_qty = self.foreign_qty * self.fx_rate
self._calculating = None
@observe('domestic_qty')
def on_domestic(self, change):
if self._calculating is None:
self.calc_foreign()
@observe('foreign_qty')
def on_foreign(self, change):
if self._calculating is None:
self.calc_domestic()
@observe('fx_rate')
def on_fxrate(self, change):
if self.lock == 'domestic':
self.calc_foreign()
else:
self.calc_domestic()
以及相应的基于 "print" 的简单视图:
class FXView:
def __init__(self, model):
self.model = model
def show(self):
print("""
domestic_qty: {:.4g}
foreign_qty: {:.4g}
fx_rate: {:.4g}
lock: {}""".format(
self.model.domestic_qty,
self.model.foreign_qty,
self.model.fx_rate,
self.model.lock
))
工作原理如下:
>> fx_model = FXModel(domestic_qty = 100., fx_rate = 200.)
>> fx_view = FXView(fx_model)
>> fx_view.show()
domestic_qty: 100
foreign_qty: 0.5
fx_rate: 200
lock: domestic
>> fx_model.fx_rate = 195.
>> fx_view.show()
domestic_qty: 100
foreign_qty: 0.5128
fx_rate: 195
lock: domestic
我还使用 ipywidgets 创建了一个视图:
import ipywidgets as widgets
domestic_label = widgets.Label("Domestic quantity")
domestic_field = widgets.FloatText()
foreign_label = widgets.Label("Foreign quantity")
foreign_field = widgets.FloatText()
fx_label = widgets.Label("Exchange rate (domestic/foreign)")
fx_field = widgets.FloatText()
lock_label = widgets.Label("If rates change, keep ")
lock_field = widgets.Dropdown(options=["domestic", "foreign"])
lock_label_post = widgets.Label('fixed')
ipyview = widgets.HBox([widgets.VBox([domestic_label, foreign_label, fx_label, lock_label]),
widgets.VBox([domestic_field, foreign_field, fx_field, widgets.HBox([lock_field, lock_label_post])])])
看起来真不错:
我的问题是;我如何 "bind" 我的模型和我的 ipyview 在一起?我对 enaml
有一些经验,这可以通过操作员 :=
和朋友实现。
使用 ipywidgets
执行此操作的最佳方法是什么?
好问题!我 post 在 ipywidgets 问题中编辑了一个答案:https://github.com/jupyter-widgets/ipywidgets/issues/2296
下面是如何使用 post 中的小部件使用 ipywidget 视图制作自定义视图。关键是 link 调用以将属性绑定到小部件值。
import ipywidgets as widgets
from traitlets import link
from IPython.display import display
class FXWidgetView:
def __init__(self, model):
self.model = model
self.domestic_label = widgets.Label("Domestic quantity")
self.domestic_field = widgets.FloatText()
self.foreign_label = widgets.Label("Foreign quantity")
self.foreign_field = widgets.FloatText()
self.fx_label = widgets.Label("Exchange rate (domestic/foreign)")
self.fx_field = widgets.FloatText()
self.lock_label = widgets.Label("If rates change, keep ")
self.lock_field = widgets.Dropdown(options=["domestic", "foreign"])
self.lock_label_post = widgets.Label('fixed')
self.ipyview = widgets.HBox([widgets.VBox([self.domestic_label, self.foreign_label, self.fx_label, self.lock_label]),
widgets.VBox([self.domestic_field, self.foreign_field, self.fx_field, widgets.HBox([self.lock_field, self.lock_label_post])])])
link((model, 'domestic_qty'), (self.domestic_field, 'value'))
link((model, 'foreign_qty'), (self.foreign_field, 'value'))
link((model, 'fx_rate'), (self.fx_field, 'value'))
link((model, 'lock'), (self.lock_field, 'value'))
def _ipython_display_(self):
display(self.ipyview)
我要指出,Param 提倡这种分离有一段时间了,@jbednar 指出 ipywidgets 也可以实现便利功能来支持这种模式。我认为这是一个好主意 - 拥有一些简单的便利功能,这些功能比采用 HasTraits class 的交互功能更进一步,对其进行内省,并为常见情况的不同特征提供默认小部件。