在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
相关产品推荐
相关产品推荐

