В чем разница между идиомами для представления изображения Matplotlib с помощью Flask?

Поиск в Интернете приводит несколько простых (недокументированных) примеров (и хороших ответов здесь) о том, как динамически обслуживать фигуры Matplotlib с помощью Flask; но есть некоторые из них, и различия между ними меня озадачивают.

Некоторые используют IO низкого уровня и возвращают кортежи

io = StringIO.StringIO()
plt.savefig(io, format='png')
io.seek(0)
data = io.read()
return data, 200, {'Content-type': 'image/png'}

в то время как несколько других используют разные IO API и возвращают 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

а третьи используют другой подход к кодированию и построению возвращаемого значения

io = StringIO.StringIO()
fig.savefig(io, format='png')
data = io.getvalue().encode('base64')
return html.format(data)

Все это, кажется, работает; но мне интересно, есть ли особенности подходов, которые они разделяют, или различия между ними, которые имеют неочевидные последствия (например, для производительности или применимости к различным сценариям).

Первый,

  • Какую роль играет StringIO; это единственный способ подготовить к подаче изображения (любого вида)?

В моей защищенной жизни Python я никогда не видел его раньше, и неясно, почему он кажется обязательной частью процесса сервера (двоичный файл?).

Во-вторых, мне интересно узнать о различных подходах, которые эти примеры используют для упаковки своего ответа; конкретно

  • есть ли какое-либо значение для использования seek плюс read, против getvalueили они делают по существу то же самое;
  • что определяет выбор между подходами к тому, что возвращается: кортеж против html.format против Responsemake_response); и наконец
  • почему некоторые подходы устанавливают Content-type явно, в то время как другие устанавливают кодировку ("base64")?

Считается ли какой-либо из этих подходов "лучшим" или наиболее актуальным идиоматическим (или хотя бы питонским) подходом?

1 ответ

Решение

какую роль играет StringIO; это единственный способ подготовить к подаче изображения (любого вида)?

Прежде всего, нет, это не единственный способ. "Классическим" способом было бы задействовать файловую систему:

  1. Пусть matplotlib создаст сюжет.
  2. Постоянно сохраняйте соответствующие данные изображения в файл в файловой системе (что включает переключение контекста в ядро, которое вызывает системные вызовы, такие как write()).
  3. Прочитайте содержимое этого файла еще раз (что позволяет ядру считывать для вас файловую систему через read()).
  4. Передайте содержимое клиенту в ответе HTTP с четко определенной кодировкой данных, а также с правильно установленными заголовками.

Шаги (3) и (4) включают взаимодействие с файловой системой. То есть ядро ​​фактически общается с аппаратными компонентами. Это требует времени (при использовании классических приводов hrad запись всего нескольких байтов на диск может занять пару миллисекунд из-за длительного времени доступа). Теперь возникает вопрос: нужно ли сохранять данные изображения на диске? Если ответ "нет", то вы можете пропустить все взаимодействие с файловой системой и сэкономить некоторое время, сохраняя данные изображения в памяти процесса веб-приложения. Что это StringIO хорошо для:

StringIO это очень универсальный инструмент в Python, который предоставляет файловые объекты, в то время как фактические данные никогда не делегируются ядру для записи их в файловую систему или чтения из файловой системы. Он хранится в памяти. Вот почему объекты StringIO также называются файлами в памяти.

Дело в том, что plt.savefig() хочет иметь объект в качестве первого аргумента, который выглядит как объект, который фактически представляет реальный файл в файловой системе. StringIO предоставляет такой объект, но - под капотом - записывает данные в буфер в куче текущего процесса и читает его оттуда снова, если требуется.

Чтение / запись небольших порций данных через StringIO занимает наносекунды или микросекунды, тогда как взаимодействие с файловой системой обычно на несколько порядков медленнее.

Не поймите меня неправильно: обычно файловая система достаточно быстрая, и у операционной системы есть свои собственные методы, позволяющие максимально быстро взаимодействовать с файловой системой. Реальный вопрос, как было сказано ранее: нужны ли вам данные изображения? Если вам не нужен доступ к данным изображения в какой-то момент позже, не включайте файловую систему. Это то, что решили создатели трех фрагментов, которые вы показываете.

Замена реального взаимодействия файловой системы со StringIO по соображениям производительности может быть очень правильным решением. Однако в вашем веб-приложении есть и другие узкие места. Например, использование StringIO может уменьшить задержку запроса-ответа, скажем, на 5 мс. Но действительно ли это имеет значение, учитывая сетевые задержки в 100 мс? Кроме того, помните, что серьезному веб-приложению лучше не беспокоиться об отправке большого файлового содержимого - оно лучше обслуживается хорошо зарекомендовавшим себя веб-сервером, который также может использовать sendfile() системный вызов. В этом случае снова может быть лучше с точки зрения производительности позволить matplotlib записать файл в файловую систему, а затем сообщить об этом вашему веб-серверу (через X-Sendfile заголовок), чтобы сделать все остальное. Таким образом, производительность - сложная тема, которая может быть не самым сильным аргументом. Но только вы знаете свои требования!

Есть ли какое-либо значение для использования поиска плюс чтения, против getvalue, или они делают по сути то же самое

По сути то же самое. Не имеет принципиального значения, не имеет (значительного) различия в производительности.

что определяет выбор между подходами для того, что возвращается: кортеж против html.format против ответа (с make_response); и наконец

Нет определенного ответа. Есть много способов передать данные клиенту. Не существует "правильного" подхода, лучше или хуже. Какой подход лучше выбрать, сильно зависит от веб-фреймворка. С колбой, make_response() канонический способ создания объекта ответа. html.format() возможно, у меня есть некоторые преимущества, о которых я не знаю - вам нужно прочитать об этом самим! Но, читайте дальше, я думаю, что есть метод, встроенный в Flask, который идеально подходит для вашего сценария.

почему некоторые подходы явно устанавливают тип контента, а другие устанавливают кодировку (в "base64")?

Существуют правильные и неправильные способы отправки файлов в браузеры через HTTP. Как правило, ответ HTTP должен содержать определенные заголовки (см. Также раздел " Какие заголовки ответа HTTP требуются"). Просто для вашего понимания, вы можете прочитать об этих деталях. Конечно, двоичные данные должны быть закодированы с кодировкой, понятной клиенту, и кодировка должна быть уточнена в заголовке ответа. Кроме того, правильный HTTP-ответ должен содержать тип MIME (тип содержимого). Представленные вами методы, по-видимому, на самом деле не контролируют одно или другое (без обид, быстрые и грязные примеры часто фокусируются больше на одном, чем на другом).

Я думаю, что вы действительно должны использовать метод Flask send_file, который позаботится о некоторых важных для вас вещах. У этого метода есть пара аргументов. Я бы явно определил тип MIME через mimetype, Первый аргумент может быть файловым объектом, поэтому объект StringIO работает нормально. Однако в этом случае вам нужно сделать seek(0) до:

Убедитесь, что указатель файла расположен в начале данных для отправки перед вызовом send_file().

Следующие два подхода семантически элегантны (на мой взгляд) и должны должным образом заботиться о кодировании содержимого файла и настройке заголовков ответа HTTP:

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) может при правильной настройке передать файл для вас.

Другие вопросы по тегам