You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Bokeh应用中通过内存流实现文件下载,无需写入本地临时文件

Bokeh 内存流实现文件下载解决方案

错误原因

你遇到的序列化报错是因为Bokeh的CustomJS的args参数仅支持可JSON序列化的数据类型,Python的BytesIO内存对象无法直接序列化传递给前端JavaScript。

修复代码

我们可以在Python端将内存流内容编码为base64字符串(可序列化),前端JS接收后再转回Blob对象触发下载,同时修复原有JS代码的兼容和内存泄漏问题,完整实现如下:

import base64
import io

def save_file_via_memory(self, save_function, file_name, mime_type=None):
    """
    :param save_function: 可调用对象,接收一个可写的类文件对象作为参数
    :param file_name: 下载的默认文件名
    :param mime_type: 文件的MIME类型,不传则根据文件名后缀自动推断
    """
    # 自动推断常见格式的MIME类型
    if not mime_type:
        suffix = file_name.split('.')[-1].lower()
        mime_map = {
            'csv': 'text/csv;charset=utf-8',
            'png': 'image/png',
            'jpg': 'image/jpeg',
            'jpeg': 'image/jpeg'
        }
        mime_type = mime_map.get(suffix, 'application/octet-stream')
    
    js_download = """
        // Base64字符串转Blob工具函数
        function b64toBlob(b64Data, contentType, sliceSize=512) {
            const byteCharacters = atob(b64Data);
            const byteArrays = [];
            for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
                const slice = byteCharacters.slice(offset, offset + sliceSize);
                const byteNumbers = new Array(slice.length);
                for (let i = 0; i < slice.length; i++) {
                    byteNumbers[i] = slice.charCodeAt(i);
                }
                const byteArray = new Uint8Array(byteNumbers);
                byteArrays.push(byteArray);
            }
            return new Blob(byteArrays, {type: contentType});
        }
        
        const blob = b64toBlob(data_b64, mime_type);
        // 兼容IE浏览器
        if (navigator.msSaveBlob) {
            navigator.msSaveBlob(blob, save_name);
        } else {
            const link = document.createElement('a');
            link.href = URL.createObjectURL(blob);
            link.download = save_name;
            link.target = '_blank';
            link.style.visibility = 'hidden';
            // 部分浏览器要求元素挂载到DOM才能触发点击
            document.body.appendChild(link);
            link.dispatchEvent(new MouseEvent('click'));
            // 清理DOM和内存
            document.body.removeChild(link);
            URL.revokeObjectURL(link.href);
        }
    """
    with io.BytesIO() as buf:
        # 将文件内容写入内存流
        save_function(buf)
        # 内存流转Base64编码字符串
        buf.seek(0)
        base64_str = base64.b64encode(buf.read()).decode('utf-8')
    
    self.exectute_js(CustomJS(args=dict(
        data_b64=base64_str,
        save_name=file_name,
        mime_type=mime_type
    ), code=js_download))

注意事项

  • 若你原有save_function是接收文件路径写入磁盘的,需要调整为支持写入类文件对象:例如Pandas导出CSV改为df.to_csv(buf, index=False),导出PNG改为plt.savefig(buf, format='png')即可,无需额外修改。
  • 代码内置了常见格式的MIME类型自动推断,特殊格式可以手动传入mime_type参数。
  • JS部分修复了原有逻辑的兼容问题,新增了DOM挂载、元素清理、对象URL释放逻辑,避免内存泄漏。

内容的提问来源于stack exchange,提问作者Siyh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 16:48:03