15

我想在 Ipython Notebook 3.x 或 4.x(Jupyter,Python 3)中制作一个用于远程文件上传的小部件,它允许用户在上传时在浏览器的文件选择器中选择多个文件。不幸的是,我对 javascript 方面一无所知。

我找到了 blueimp 的小部件,但是,我不知道如何在笔记本中使用它们。

是制作单个文件上传小部件的方式:

import base64
from __future__ import print_function # py 2.7 compat.
from IPython.html import widgets # Widget definitions.
from IPython.utils.traitlets import Unicode # Traitlet needed to add synced attributes to the widget.
class FileWidget(widgets.DOMWidget):
    _view_name = Unicode('FilePickerView', sync=True)
    value = Unicode(sync=True)
    filename = Unicode(sync=True)

    def __init__(self, **kwargs):
        """Constructor"""
        widgets.DOMWidget.__init__(self, **kwargs) # Call the base.

        # Allow the user to register error callbacks with the following signatures:
        #    callback()
        #    callback(sender)
        self.errors = widgets.CallbackDispatcher(accepted_nargs=[0, 1])

        # Listen for custom msgs
        self.on_msg(self._handle_custom_msg)

    def _handle_custom_msg(self, content):
        """Handle a msg from the front-end.

        Parameters
        ----------
        content: dict
            Content of the msg."""
        if 'event' in content and content['event'] == 'error':
            self.errors()
            self.errors(self)
%%javascript

require(["widgets/js/widget", "widgets/js/manager"], function(widget, manager){

    var FilePickerView = widget.DOMWidgetView.extend({
        render: function(){
            // Render the view.
            this.setElement($('<input />')
                .attr('type', 'file'));
        },

        events: {
            // List of events and their handlers.
            'change': 'handle_file_change',
        },

        handle_file_change: function(evt) { 
            // Handle when the user has changed the file.

            // Retrieve the first (and only!) File from the FileList object
            var file = evt.target.files[0];
            if (file) {

                // Read the file's textual content and set value to those contents.
                var that = this;
                var file_reader = new FileReader();
                file_reader.onload = function(e) {
                    that.model.set('value', e.target.result);
                    that.touch();
                }
                file_reader.readAsText(file);
            } else {

                // The file couldn't be opened.  Send an error msg to the
                // back-end.
                this.send({ 'event': 'error' });
            }

            // Set the filename of the file.
            this.model.set('filename', file.name);
            this.touch();
        },
    });

    // Register the FilePickerView with the widget manager.
    manager.WidgetManager.register_widget_view('FilePickerView', FilePickerView);
});
file_widget = FileWidget()

# Register an event to echo the filename when it has been changed.
def file_loading():
    print("Loading %s" % file_widget.filename)
file_widget.on_trait_change(file_loading, 'filename')

# Register an event to echo the filename and contents when a file
# has been uploaded.
def file_loaded():
    print("Loaded, file contents: %s" % file_widget.value)
file_widget.on_trait_change(file_loaded, 'value')

# Register an event to print an error message when a file could not
# be opened.  Since the error messages are not handled through
# traitlets but instead handled through custom msgs, the registration
# of the handler is different than the two examples above.  Instead
# the API provided by the CallbackDispatcher must be used.
def file_failed():
    print("Could not load file contents of %s" % file_widget.filename)
file_widget.errors.register_callback(file_failed)

file_widget
4

3 回答 3

4

欢迎评论、建议和修复。

我从notebooklist.js文件的NotebookList.prototype.handleFilesUpload功能中获得了 Jupyter Notebook (4.x) 本身的灵感。在阅读了一些 javascript 语法之后,我想出了以下内容:

(请注意,文件以文本模式上传,无需检查。)

import base64 # You need it if you define binary uploads
from __future__ import print_function # py 2.7 compat.
import ipywidgets as widgets # Widget definitions.
from traitlets import List, Unicode  # Traitlets needed to add synced attributes to the widget.

class FileWidget(widgets.DOMWidget):
    _view_name = Unicode('FilePickerView').tag(sync=True)
    _view_module = Unicode('filepicker').tag(sync=True)
    filenames = List([], sync=True)
    # values = List(trait=Unicode, sync=True)

    def __init__(self, **kwargs):
        """Constructor"""
        super().__init__(**kwargs)

        # Allow the user to register error callbacks with the following signatures:
        #    callback()
        #    callback(sender)
        self.errors = widgets.CallbackDispatcher(accepted_nargs=[0, 1])

        # Listen for custom msgs
        self.on_msg(self._handle_custom_msg)

    def _handle_custom_msg(self, content):
        """Handle a msg from the front-end.

        Parameters
        ----------
        content: dict
            Content of the msg."""
        if 'event' in content and content['event'] == 'error':
            self.errors()
            self.errors(self)
%%javascript
requirejs.undef('filepicker');

define('filepicker', ["jupyter-js-widgets"], function(widgets) {

    var FilePickerView = widgets.DOMWidgetView.extend({
        render: function(){
            // Render the view using HTML5 multiple file input support.
            this.setElement($('<input class="fileinput" multiple="multiple" name="datafile"  />')
                .attr('type', 'file'));
        },

        events: {
            // List of events and their handlers.
            'change': 'handle_file_change',
        },

        handle_file_change: function(evt) { 
            // Handle when the user has changed the file.

            // Save context (or namespace or whatever this is)
            var that = this;

            // Retrieve the FileList object
            var files = evt.originalEvent.target.files;
            var filenames = [];
            var file_readers = [];
            console.log("Reading files:");

            for (var i = 0; i < files.length; i++) {
                var file = files[i];
                console.log("Filename: " + file.name);
                console.log("Type: " + file.type);
                console.log("Size: " + file.size + " bytes");
                filenames.push(file.name);

                // Read the file's textual content and set value_i to those contents.
                file_readers.push(new FileReader());
                file_readers[i].onload = (function(file, i) {
                    return function(e) {
                        that.model.set('value_' + i, e.target.result);
                        that.touch();
                        console.log("file_" + i + " loaded: " + file.name);
                    };
                })(file, i);

                file_readers[i].readAsText(file);
            }

            // Set the filenames of the files.
            this.model.set('filenames', filenames);
            this.touch();
        },
    });

    // Register the FilePickerView with the widget manager.
    return {
        FilePickerView: FilePickerView
    };
});
file_widget = FileWidget()

def file_loaded(change):
    '''Register an event to save contents when a file has been uploaded.'''
    print(change['new'])
    i = int(change['name'].split('_')[1])
    fname = file_widget.filenames[i]
    print('file_loaded: {}'.format(fname))

def file_loading(change):
    '''Update self.model when user requests a list of files to be uploaded'''
    print(change['new'])
    num = len(change['new'])
    traits = [('value_{}'.format(i), Unicode(sync=True)) for i in range(num)]
    file_widget.add_traits(**dict(traits))
    for i in range(num):
        file_widget.observe(file_loaded, 'value_{}'.format(i))
file_widget.observe(file_loading, names='filenames')

def file_failed():
    print("Could not load some file contents.")
file_widget.errors.register_callback(file_failed)


file_widget

应出现一个带有文本的按钮,Browse...说明选择了多少文件。由于print语句包含在file_loadingandfile_loaded函数中,您应该在输出中看到文件名和文件内容。文件名和文件类型也显示在控制台日志中。

于 2017-01-28T13:40:59.647 回答
3

本期https://github.com/ipython/ipython/issues/8383部分回答了您的问题。仪表板屏幕上的 jupyter 4.0 中已经有上传按钮。此上传按钮支持选择多个文件。

请注意,上传小部件的最新链接位于此处:

https://github.com/ipython/ipywidgets/blob/master/docs/source/examples/File%20Upload%20Widget.ipynb

您的笔记本中还有一个可供下载和快速安装的扩展:

https://github.com/peteut/ipython-file-upload

pip install fileupload

或者

pip install git+https://github.com/peteut/ipython-file-upload

请注意,根据作者的说法,该扩展已确认仅可在 linux 上运行。

于 2015-09-09T22:31:36.593 回答
3

有一种比 fileupload 更新的方法,我过去使用过,并且使用原生支持的文件上传小部件工作得很好(由 @denfromufa 发布)。

import io
import ipywidgets as widgets

widgets.FileUpload(
        accept='.txt',  # Accepted file extension e.g. '.txt', '.pdf', 'image/*', 'image/*,.pdf'
        multiple=True  # True to accept multiple files upload else False
    )

几个提示:

  1. 如果您的笔记本在上传器“下”有代码,它将继续。我使用 ipython 阻塞来“暂停”笔记本其余部分的执行,直到文件成功上传......然后我有第二个按钮,基本上在用户上传正确的文件并准备好后重新启动代码执行继续。
  2. ipywidget 上传器与 [fileupload] (pip install fileupload) 的不同之处在于它将文件拉入 python,以便您可以使用它。如果要将其放入目录中,则必须使用其他方法将值数据写入文件:

对于文件上传小部件“myupload”,您可以编写一个事件驱动的函数,该函数在文件上传时包括以下内容:

    # Get the original filename which is the first key in the widget's value dict:
    uploaded_filename = next(iter(myupload.value))
    content = myupload.value[uploaded_filename]['content']
    with open('myfile', 'wb') as f: f.write(content)
于 2019-09-18T20:33:48.413 回答