使用 Flask 提供 Matplotlib 图像的习语有什么区别?
What is the difference among idioms for serving a Matplotlib image with Flask?
Web 搜索找到了几个简单的(未记录的)示例(很好 answers here 关于)如何使用 Flask 动态提供 Matplotlib 图形;但是它们有一些特点,它们之间的差异让我很困惑。
Some 使用低级 IO 和 return 元组
io = StringIO.StringIO()
plt.savefig(io, format='png')
io.seek(0)
data = io.read()
return data, 200, {'Content-type': 'image/png'}
而 several others 使用不同的 IO API 和 return Response
io = StringIO.StringIO()
canvas = FigureCanvas(fig)
canvas.print_png(io)
response = make_response(io.getvalue())
response.mimetype = 'image/png' # or response.headers['Content-Type'] = 'image/png'
return response
但 others 采用不同的方法来编码和构建 return 值
io = StringIO.StringIO()
fig.savefig(io, format='png')
data = io.getvalue().encode('base64')
return html.format(data)
所有这些似乎都有效;但我想知道它们是否共享方法的特征,或者它们之间的差异具有不明显的后果(例如,性能或对不同场景的适用性)。
首先,
StringIO
的作用是什么;这是准备提供图像(任何类型)的唯一方法吗?
在我受保护的 Python 生活中,我以前从未见过它被使用过,并且不清楚为什么它似乎是服务器 a(二进制?)文件进程的必需部分。
其次,我想知道这些示例采用不同的方法来打包他们的响应;特别是
- 使用
seek
加上 read
与 getvalue
相比有什么意义吗,或者它们本质上做同样的事情;
- 什么决定了 returned 方法的选择:元组与
html.format
与 Response
(使用 make_response
);最后
- 为什么有些方法明确设置
Content-type
,而其他方法设置编码('base64')?
这些方法中的任何一种是否被认为是 "best" 或最新的惯用(或至少 Pythonic)方法?
what is the role played by StringIO; is it the only way to prepare to
serve an image (of any kind)?
首先,不,这不是唯一的方法。 "classical" 方法是涉及文件系统:
- 让 matplotlib 创建绘图。
- 将相应的图像数据持久保存到文件系统中的文件中(涉及到内核的上下文切换,内核调用系统调用,如
write()
)。
- 再次读取此文件的内容(让内核通过
read()
为您读出文件系统)。
- 在使用 well-defined 数据编码并正确设置 headers 的 HTTP 响应中将内容提供给客户端。
步骤(3)和(4)涉及文件系统交互。也就是说,内核实际上与硬件组件对话。这需要时间(对于传统的 hrad 驱动器,向磁盘写入几个字节可能需要几毫秒,因为访问时间很长)。现在,问题是:是否需要将图像数据 persisted 保存到磁盘?如果答案是 "no",那么您可以 跳过与文件系统的整个交互过程 并 节省一些时间 ,方法是保留图像Web 应用程序进程内存中的数据。这就是 StringIO
的好处:
StringIO
是 Python 中非常通用的工具,它提供 file-like objects,而实际数据永远不会委托给内核写入文件系统或从文件系统中读取它。它保存在内存中。这就是为什么 StringIO object 也被称为 in-memory 文件。
要点是 plt.savefig()
想要 作为第一个参数,看起来像 object 实际上代表一个真实的文件系统中的文件。 StringIO
提供了这样一个 object,但在幕后将数据写入当前进程堆中的缓冲区,并在需要时再次从那里读取数据。
Reading/writing 通过 StringIO
的一小部分数据需要纳秒或微秒,而与文件系统的交互通常要慢几个数量级。
现在,请不要误会我的意思:通常,文件系统足够快,并且操作系统有自己的技术来使文件系统交互尽可能快。真正的问题是,如前所述:您是否需要图像数据 persisted?如果您不关心稍后在某个时候访问此图像数据,则不要涉及文件系统。这是您显示的三个片段的创作者决定的。
出于性能原因用 StringIO 替换真实文件系统交互 可能是一个非常非常有效的决定。但是,在您的 Web 应用程序中肯定还有其他瓶颈。例如,使用 StringIO 可以将 request-response 延迟减少 5 毫秒。但考虑到 100 毫秒的网络延迟,这真的重要吗?另外,请记住,一个严肃的网络应用程序最好不要为发送大文件内容而烦恼——这些内容最好由 well-established 网络服务器提供,它也可以使用 sendfile()
系统调用。在这种情况下,让 matplotlib 将文件写入文件系统然后告诉您的 Web 服务器(通过 X-Sendfile
header)完成其余的工作可能再次 performance-wise 更好。因此,性能是一个复杂的话题可能不是最有力的论据。但只有你知道你的要求!
is there any significance to the use of seek plus read, vs. getvalue,
or do these do essentially the same thing
本质上是一样的。不会产生概念上的差异,不会产生(显着的)性能差异。
what governs the choice among approaches for what is returned: a tuple
vs. html.format vs. a Response (with make_response); and, finally
没有确定的答案。有许多方法可以将数据获取到客户端。没有 "correct" 方法,只有更好或更坏。采取哪种方法最好很大程度上取决于 Web 框架。使用 Flask,make_response()
是创建响应 object 的规范方式。 html.format()
可能有一些我不知道的优势——您需要自己阅读!但是,请继续阅读,我认为 Flask 中内置了一种方法,非常适合您的场景。
why do some approaches set the Content-type explicitly, while others
set the encoding (to 'base64')?
通过 HTTP 将文件发送到浏览器有正确和不正确的方法。通常,HTTP 响应应包含某些 header(另请参阅 What HTTP response headers are required)。为了您的理解,您可能需要阅读这些详细信息。当然,二进制数据需要使用客户端理解的编码进行编码,并且必须在响应 header 中明确编码。此外,正确的 HTTP 响应应该包含 MIME 类型(内容类型)。您提供的方法似乎并没有真正控制其中一个(没有冒犯,快速和肮脏的例子通常更多地关注一件事而不是另一件事)。
我认为您真的应该使用 Flask 的 send_file 方法,它可以为您处理一些重要的事情。此方法有几个参数。我会通过 mimetype
显式定义 MIME 类型。第一个参数可以是 file-like object,因此 StringIO object 可以正常工作。但是,在这种情况下,您需要在seek(0)
之前做:
Make sure that the file pointer is positioned at the start of data to
send before calling send_file().
以下两种方法在语义上很优雅(在我看来)并且不应该请适当注意对文件内容进行编码并设置 HTTP 响应 headers:
from flask import send_file
1)
f = StringIO.StringIO()
plt.savefig(f, format='png', dpi=300)
f.seek(0)
send_file(f, mimetype='image/png')
2)
plt.savefig('image.png', dpi=300)
send_file('image.png', mimetype='image/png')
在第二种情况下,如果配置正确,您的网络服务器(例如 nginx)可以为您传输文件。
Web 搜索找到了几个简单的(未记录的)示例(很好 answers here 关于)如何使用 Flask 动态提供 Matplotlib 图形;但是它们有一些特点,它们之间的差异让我很困惑。
Some 使用低级 IO 和 return 元组
io = StringIO.StringIO()
plt.savefig(io, format='png')
io.seek(0)
data = io.read()
return data, 200, {'Content-type': 'image/png'}
而 several others 使用不同的 IO API 和 return Response
io = StringIO.StringIO()
canvas = FigureCanvas(fig)
canvas.print_png(io)
response = make_response(io.getvalue())
response.mimetype = 'image/png' # or response.headers['Content-Type'] = 'image/png'
return response
但 others 采用不同的方法来编码和构建 return 值
io = StringIO.StringIO()
fig.savefig(io, format='png')
data = io.getvalue().encode('base64')
return html.format(data)
所有这些似乎都有效;但我想知道它们是否共享方法的特征,或者它们之间的差异具有不明显的后果(例如,性能或对不同场景的适用性)。
首先,
StringIO
的作用是什么;这是准备提供图像(任何类型)的唯一方法吗?
在我受保护的 Python 生活中,我以前从未见过它被使用过,并且不清楚为什么它似乎是服务器 a(二进制?)文件进程的必需部分。
其次,我想知道这些示例采用不同的方法来打包他们的响应;特别是
- 使用
seek
加上read
与getvalue
相比有什么意义吗,或者它们本质上做同样的事情; - 什么决定了 returned 方法的选择:元组与
html.format
与Response
(使用make_response
);最后 - 为什么有些方法明确设置
Content-type
,而其他方法设置编码('base64')?
这些方法中的任何一种是否被认为是 "best" 或最新的惯用(或至少 Pythonic)方法?
what is the role played by StringIO; is it the only way to prepare to serve an image (of any kind)?
首先,不,这不是唯一的方法。 "classical" 方法是涉及文件系统:
- 让 matplotlib 创建绘图。
- 将相应的图像数据持久保存到文件系统中的文件中(涉及到内核的上下文切换,内核调用系统调用,如
write()
)。 - 再次读取此文件的内容(让内核通过
read()
为您读出文件系统)。 - 在使用 well-defined 数据编码并正确设置 headers 的 HTTP 响应中将内容提供给客户端。
步骤(3)和(4)涉及文件系统交互。也就是说,内核实际上与硬件组件对话。这需要时间(对于传统的 hrad 驱动器,向磁盘写入几个字节可能需要几毫秒,因为访问时间很长)。现在,问题是:是否需要将图像数据 persisted 保存到磁盘?如果答案是 "no",那么您可以 跳过与文件系统的整个交互过程 并 节省一些时间 ,方法是保留图像Web 应用程序进程内存中的数据。这就是 StringIO
的好处:
StringIO
是 Python 中非常通用的工具,它提供 file-like objects,而实际数据永远不会委托给内核写入文件系统或从文件系统中读取它。它保存在内存中。这就是为什么 StringIO object 也被称为 in-memory 文件。
要点是 plt.savefig()
想要 作为第一个参数,看起来像 object 实际上代表一个真实的文件系统中的文件。 StringIO
提供了这样一个 object,但在幕后将数据写入当前进程堆中的缓冲区,并在需要时再次从那里读取数据。
Reading/writing 通过 StringIO
的一小部分数据需要纳秒或微秒,而与文件系统的交互通常要慢几个数量级。
现在,请不要误会我的意思:通常,文件系统足够快,并且操作系统有自己的技术来使文件系统交互尽可能快。真正的问题是,如前所述:您是否需要图像数据 persisted?如果您不关心稍后在某个时候访问此图像数据,则不要涉及文件系统。这是您显示的三个片段的创作者决定的。
出于性能原因用 StringIO 替换真实文件系统交互 可能是一个非常非常有效的决定。但是,在您的 Web 应用程序中肯定还有其他瓶颈。例如,使用 StringIO 可以将 request-response 延迟减少 5 毫秒。但考虑到 100 毫秒的网络延迟,这真的重要吗?另外,请记住,一个严肃的网络应用程序最好不要为发送大文件内容而烦恼——这些内容最好由 well-established 网络服务器提供,它也可以使用 sendfile()
系统调用。在这种情况下,让 matplotlib 将文件写入文件系统然后告诉您的 Web 服务器(通过 X-Sendfile
header)完成其余的工作可能再次 performance-wise 更好。因此,性能是一个复杂的话题可能不是最有力的论据。但只有你知道你的要求!
is there any significance to the use of seek plus read, vs. getvalue, or do these do essentially the same thing
本质上是一样的。不会产生概念上的差异,不会产生(显着的)性能差异。
what governs the choice among approaches for what is returned: a tuple vs. html.format vs. a Response (with make_response); and, finally
没有确定的答案。有许多方法可以将数据获取到客户端。没有 "correct" 方法,只有更好或更坏。采取哪种方法最好很大程度上取决于 Web 框架。使用 Flask,make_response()
是创建响应 object 的规范方式。 html.format()
可能有一些我不知道的优势——您需要自己阅读!但是,请继续阅读,我认为 Flask 中内置了一种方法,非常适合您的场景。
why do some approaches set the Content-type explicitly, while others set the encoding (to 'base64')?
通过 HTTP 将文件发送到浏览器有正确和不正确的方法。通常,HTTP 响应应包含某些 header(另请参阅 What HTTP response headers are required)。为了您的理解,您可能需要阅读这些详细信息。当然,二进制数据需要使用客户端理解的编码进行编码,并且必须在响应 header 中明确编码。此外,正确的 HTTP 响应应该包含 MIME 类型(内容类型)。您提供的方法似乎并没有真正控制其中一个(没有冒犯,快速和肮脏的例子通常更多地关注一件事而不是另一件事)。
我认为您真的应该使用 Flask 的 send_file 方法,它可以为您处理一些重要的事情。此方法有几个参数。我会通过 mimetype
显式定义 MIME 类型。第一个参数可以是 file-like object,因此 StringIO object 可以正常工作。但是,在这种情况下,您需要在seek(0)
之前做:
Make sure that the file pointer is positioned at the start of data to send before calling send_file().
以下两种方法在语义上很优雅(在我看来)并且不应该请适当注意对文件内容进行编码并设置 HTTP 响应 headers:
from flask import send_file
1)
f = StringIO.StringIO()
plt.savefig(f, format='png', dpi=300)
f.seek(0)
send_file(f, mimetype='image/png')
2)
plt.savefig('image.png', dpi=300)
send_file('image.png', mimetype='image/png')
在第二种情况下,如果配置正确,您的网络服务器(例如 nginx)可以为您传输文件。