ipywidgets 中的子小部件创建使用 ViewList 和 create_child_view 产生错误

Ric*_*ica 5 javascript python backbone.js jupyter-notebook ipywidgets

概括

请参阅此链接中托管的玩具示例 Azure 笔记本。可以从那里克隆和运行笔记本,或者从那里下载并在本地运行,但为了方便起见,所有代码也在下面。

当所有单元格运行时,javascript 控制台会在最终单元格中报告这些错误(缩写),并且最终预期的输出行不会呈现:

Error: Could not create a view for model id 91700d0eb745433eaee98bca2d9f3fc8
    at promiseRejection (utils.js:119)
Error: Could not create view
    at promiseRejection (utils.js:119)
Uncaught (in promise) TypeError: Cannot read property 'then' of undefined
Uncaught (in promise) TypeError: Cannot read property 'then' of undefined
Run Code Online (Sandbox Code Playgroud)

不知道我哪里出错了。

更新:

由于它当前存在,代码将一个字符串实例(而不是一个DOMWidgetModel实例)发送到该create_child_view方法。该字符串包含"IPY_MODEL_"由模型 id 附加。这似乎可能是问题的根源。客户端正在从服务器端 Backbonechildren模型数组项 ( this.model.get('children'))接收该字符串实例。

我想知道问题是否与低级小部件教程中讨论的小部件的 [反] 序列化有关。但我不确定如何使用它来解决这个问题,因为我需要访问子小部件模型本身而不仅仅是一个属性。而且我相信我正确地传递**widgets.widget_serialization了教程指定的内容。


细节

Notebook 包含 python 和 javascript 代码,并使用ipywidgets严重依赖 Backbone的库。后端代码(python,单元格 #1)创建了一个ipywidgets.DOMWidget子类小部件Test(前端镜像的 Backbone 模型)。前端代码(javascript,单元格 #2)创建了一个ipywidgets.DOMWidgetView子类 ,TestView它在呈现到页面时由小部件实例化。

Test部件有一个模型children构件由多个“子部件”(其也是模型)组成。这些小部件是 python 类的实例Sub。当Test渲染视图时,我想实例化和渲染子小部件的视图并将它们附加到父Test小部件的视图(注意:最后一部分尚未在下面实现)。

问题是,当我尝试按照ipywidgetsAPI 创建子视图时,ViewList通过使用create_child_view每个子模型上的方法实例化子视图来填充数组不起作用。

对于这种事情的API是不是得特别好,所以我尽我所能遵循的各种类似的例子如何使用儿童模特从父视图中如父控件实例化子视图,ipywidgets本身ipyleaflet。但我所做的一切似乎都无法让创建儿童视图发挥作用。

请注意,我能够单独呈现每个Sub小部件的视图,没有任何问题。只有当我尝试使用该方法从父小部件中创建视图时,我们才会遇到问题。create_child_viewTest


代码

单元 1(服务器端 jupyter python 内核)

Error: Could not create a view for model id 91700d0eb745433eaee98bca2d9f3fc8
    at promiseRejection (utils.js:119)
Error: Could not create view
    at promiseRejection (utils.js:119)
Uncaught (in promise) TypeError: Cannot read property 'then' of undefined
Uncaught (in promise) TypeError: Cannot read property 'then' of undefined
Run Code Online (Sandbox Code Playgroud)

Cell 2(前端jupyter notebook代码)

%%javascript

require.undef('test');

define('test', ["@jupyter-widgets/base"], function(widgets) {

    var SubView = widgets.DOMWidgetView.extend({

        initialize: function() {
            console.log('init SubView');
            SubView.__super__.initialize.apply(this, arguments);
        },

        render: function() {
            this.el.textContent = "subview rendering";
        },

    });

    var TestView = widgets.DOMWidgetView.extend({

        initialize: function() {
            console.log('init TestView');
            TestView.__super__.initialize.apply(this, arguments);
            this.views = new widgets.ViewList(this.add_view, null, this);
            this.listenTo(this.model, 'change:children', function(model, value) {
                this.views.update(value);
            }, this);
            console.log('init TestView complete');
        },

        add_view: function (child_model) {
            // error occurs on this line:
            return this.create_child_view(child_model);
        },

        render: function() {
            this.views.update(this.model.get('children'));
            this.el.textContent = 'rendered test_view';
        },
    });

    return {
        SubView : SubView,
        TestView : TestView,
    };

});
Run Code Online (Sandbox Code Playgroud)

Cell 3(用于测试的python代码)

import ipywidgets.widgets as widgets
from traitlets import Unicode, List, Instance
from IPython.display import display


class Sub(widgets.DOMWidget):
    """Widget intended to be part of the view of another widget."""
    _view_name = Unicode('SubView').tag(sync=True)
    _view_module = Unicode('test').tag(sync=True)
    _view_module_version = Unicode('0.1.0').tag(sync=True)


class Test(widgets.DOMWidget):
    """A parent widget intended to be made up of child widgets."""
    _view_name = Unicode('TestView').tag(sync=True)
    _view_module = Unicode('test').tag(sync=True)
    _view_module_version = Unicode('0.1.0').tag(sync=True)
    children = List(Instance(widgets.Widget)).tag(sync=True, 
                                        **widgets.widget_serialization)

    def __init__(self, subs):
        super().__init__()
        self.children = list(subs)
Run Code Online (Sandbox Code Playgroud)

输出

电流输出:

subview rendering  

subview rendering  

subview rendering  

subview rendering
Run Code Online (Sandbox Code Playgroud)

预期输出:

subview rendering  

subview rendering  

subview rendering  

subview rendering  

rendered test_view
Run Code Online (Sandbox Code Playgroud)

如果有人感兴趣,有关我正在从事的实际项目的更多具体信息在此 github 问题中

Pas*_*ion 3

您需要明确告诉前端如何反序列化小部件,即如何将字符串转换"IPY_MODEL_*"为实际模型。

您可以通过在前端显式定义模型并为属性设置自定义反序列化器来实现此目的children。这与您在 Python 端**widgets.widget_serialization添加到 Traitlet 的序列化器相对应。children

笔记本

这是渲染子项的笔记本的修改版本:

https://gist.github.com/pbugnion/63cf43b41ec0eed2d0b7e7426d1c67d2

全面改动

内核端,维护对 JS 模型类的显式引用:

class Test(widgets.DOMWidget):
    _model_name = Unicode('TestModel').tag(sync=True)  # reference to JS model class
    _model_module = Unicode('test').tag(sync=True)  # reference to JS model module

    # all the rest is unchanged
    _view_name = Unicode('TestView').tag(sync=True)
    _view_module = Unicode('test').tag(sync=True)
    _view_module_version = Unicode('0.1.0').tag(sync=True)
    children = List(Instance(widgets.Widget)).tag(sync=True, **widgets.widget_serialization)

    def __init__(self, subs):
        super().__init__()
        self.children = subs
Run Code Online (Sandbox Code Playgroud)

然后,在 JS 方面,

  1. 导入下划线以便我们可以扩展对象:
require.undef('test');

define('test', ["@jupyter-widgets/base", "underscore"], function(widgets, _) {

Run Code Online (Sandbox Code Playgroud)
  1. 定义您的模型模块:
    var TestModel = widgets.DOMWidgetModel.extend({}, {
        serializers: _.extend({
            children: { deserialize: widgets.unpack_models }
        }, widgets.WidgetModel.serializers)
    })
Run Code Online (Sandbox Code Playgroud)

widgets.unpack_models这告诉小部件管理器在反序列化属性时使用该函数children。我们也许可以Object.assign在这里使用而不是下划线,这将消除下划线依赖性。

  1. 导出您的模型:
    return {
        SubView : SubView,
        TestView : TestView,
        TestModel : TestModel
    };
Run Code Online (Sandbox Code Playgroud)

野外的例子

我可以在此处的IPyleaflet 代码库中找到与此匹配的模式。课堂上专门看一下LeafletLayerModel

对于使用更现代(babelified)语法的示例,我的包在此处gmaps使用小部件反序列化。