如何在 Ipython Notebook 中制作多文件上传小部件?
How to make multi-file upload widget in Ipython Notebook?
我想在 Ipython Notebook 3.x 或 4.x(Jupyter,Python 3)中制作一个小部件,用于远程文件上传,允许用户 select 上传时浏览器文件选择器中的多个文件。不幸的是,我对 javascript 方面一无所知。
我找到了 blueimp's widgets,但是,我不知道如何在笔记本中使用它们。
This 单个文件上传小部件是这样制作的:
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
本期 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 上有效。
欢迎提出意见、建议和修正。
我从 Jupyter Notebook (4.x) 本身的 notebooklist.js 文件的 NotebookList.prototype.handleFilesUpload
函数中获得灵感。在阅读了一些 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_loading
和 file_loaded
函数中,您应该在输出中看到文件名和文件内容。文件名和文件类型也显示在控制台日志中。
有一种比 fileupload 更新的方法,我过去使用过它并且效果很好(由@denfromufa 发布)natively supported file-upload widget.
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
)
一些提示:
- 如果您的笔记本有代码 'under' 上传者,它将继续。我使用 ipython 阻止 'pause' 笔记本其余部分的执行,直到文件成功上传......然后我有第二个按钮,在用户上传正确的文件后基本上重新启动代码执行(s) 并准备继续。
- 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)
我想在 Ipython Notebook 3.x 或 4.x(Jupyter,Python 3)中制作一个小部件,用于远程文件上传,允许用户 select 上传时浏览器文件选择器中的多个文件。不幸的是,我对 javascript 方面一无所知。
我找到了 blueimp's widgets,但是,我不知道如何在笔记本中使用它们。
This 单个文件上传小部件是这样制作的:
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
本期 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 上有效。
欢迎提出意见、建议和修正。
我从 Jupyter Notebook (4.x) 本身的 notebooklist.js 文件的 NotebookList.prototype.handleFilesUpload
函数中获得灵感。在阅读了一些 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_loading
和 file_loaded
函数中,您应该在输出中看到文件名和文件内容。文件名和文件类型也显示在控制台日志中。
有一种比 fileupload 更新的方法,我过去使用过它并且效果很好(由@denfromufa 发布)natively supported file-upload widget.
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
)
一些提示:
- 如果您的笔记本有代码 'under' 上传者,它将继续。我使用 ipython 阻止 'pause' 笔记本其余部分的执行,直到文件成功上传......然后我有第二个按钮,在用户上传正确的文件后基本上重新启动代码执行(s) 并准备继续。
- 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)