【问题标题】:How to download a file using ipywidget button?如何使用 ipywidget 按钮下载文件?
【发布时间】:2020-08-25 18:06:23
【问题描述】:

我构建了一个 ipywidget 按钮。我希望当单击按钮时,程序进行计算,并得到一个结果字符串,然后用户可以将字符串下载为文件。

代码是这样的:

import ipywidgets as widgets


download_button = widgets.ToggleButton()
download_button.on_click(do_some_work)

def do_some_work(content)-> str:
    res = compute()
    # Here comes the problem: how to let user download the res as a file?

def compute()-> str:
    # ... do_some_compute
    return res

我已阅读ipywidgets doc 很多次,但找不到解决方案。

我现在使用另一种方法(这严重影响了用户体验):创建一个 HTML 小部件,当点击 download_button 时,将 HTML 小部件的值更改为指向data:text/plain;charset=utf-8,{res} 的链接,让用户点击和下载,但是有什么方法可以一键实现?

任何帮助将不胜感激。

【问题讨论】:

    标签: python python-3.x jupyter-notebook ipywidgets


    【解决方案1】:

    游戏晚了,但如果其他人面临这个问题并需要动态文件内容,这里有一种方法。该代码的灵感来自@Poompil 的答案。此外,可能有更优雅的方式绕过浏览器缓存,但无法使其在 Jupyter 中工作。

    import base64
    import hashlib
    from typing import Callable
    
    import ipywidgets
    from IPython.display import HTML, display
    
    
    class DownloadButton(ipywidgets.Button):
        """Download button with dynamic content
    
        The content is generated using a callback when the button is clicked.
        """
    
        def __init__(self, filename: str, contents: Callable[[], str], **kwargs):
            super(DownloadButton, self).__init__(**kwargs)
            self.filename = filename
            self.contents = contents
            self.on_click(self.__on_click)
    
        def __on_click(self, b):
            contents: bytes = self.contents().encode('utf-8')
            b64 = base64.b64encode(contents)
            payload = b64.decode()
            digest = hashlib.md5(contents).hexdigest()  # bypass browser cache
            id = f'dl_{digest}'
    
            display(HTML(f"""
    <html>
    <body>
    <a id="{id}" download="{self.filename}" href="data:text/csv;base64,{payload}" download>
    </a>
    
    <script>
    (function download() {{
    document.getElementById('{id}').click();
    }})()
    </script>
    
    </body>
    </html>
    """))
    
    

    现在我们可以简单地添加

    DownloadButton(filename='foo.txt', contents=lambda: f'hello {time.time()}', description='download')
    

    其中添加了一个下载按钮,并且在按钮计时时生成下载文件的内容。

    【讨论】:

    • 这很棒——我现在已经开始在我的实现中使用它了。谢谢!
    • 这在 Jupyter 中效果很好,但在 JupyterLab 中不起作用(使用 3.2.5 测试)。知道如何让它在 JupyterLab 中工作吗?
    【解决方案2】:

    我见过的最优雅的方式是解决方案 1 here(稍作修改并在下面展示):

    from ipywidgets import HTML
    from IPython.display import display
    
    import base64
    
    res = 'computed results'
    
    #FILE
    filename = 'res.txt'
    b64 = base64.b64encode(res.encode())
    payload = b64.decode()
    
    #BUTTONS
    html_buttons = '''<html>
    <head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    </head>
    <body>
    <a download="{filename}" href="data:text/csv;base64,{payload}" download>
    <button class="p-Widget jupyter-widgets jupyter-button widget-button mod-warning">Download File</button>
    </a>
    </body>
    </html>
    '''
    
    html_button = html_buttons.format(payload=payload,filename=filename)
    display(HTML(html_button))
    

    【讨论】:

    • 谢谢你!但我不确定这对我有多大帮助。在我的情况下,初始化按钮时文件不存在,而是在单击按钮后计算。我实际上正在寻找一种让按钮同时触发计算和下载的方法。
    猜你喜欢
    • 2017-03-05
    • 1970-01-01
    • 2015-01-07
    • 2021-06-01
    • 1970-01-01
    • 2012-04-25
    • 2021-06-15
    • 1970-01-01
    • 2019-02-17
    相关资源
    最近更新 更多