はじめに
先日、Streamlitで作ったアプリをRenderにデプロイしたところ、ローカルでは動いたのに本番環境では画像読み込みでエラーが出ました。
調べて修正していくうちに、ファイルバッファの仕組みを理解する良いきっかけになったので共有します。
ローカルで動いても本番で落ちる理由
Streamlitのst.file_uploader
で受け取れるのは「ファイルそのもの」ではなく、**ファイルバッファ(file buffer)**と呼ばれるオブジェクトです。
- ファイルバッファ = ファイルを読むための窓口(ストリーム+読み取り位置つき)
-
.read()
を呼ぶと「しおり」が進み、次は続きからしか読めない
ローカル環境ではたまたま問題が出なくても、本番環境(Renderなど)ではファイルの扱い方が少し違うため、**「2回目に読めない」「I/Oエラー」**といった不具合が表面化します。
解決のポイント
-
バイト列に吸い上げる
file_bytes = file_buffer.read() pil_image = Image.open(io.BytesIO(file_bytes))
→ 生データをコピーすることで、環境の差を吸収。
-
しおりをリセットする
file_buffer.seek(0)
→ 同じ
file_buffer
を後で使うなら、必ず先頭に戻す。 -
例外をユーザーに見せる
except Exception as e: st.error(f"画像の読み込みに失敗しました: {str(e)}") st.stop()
→ 黒画面クラッシュを防ぎ、原因がわかるようにする。
最終コード(抜粋)
def load_image_as_bgr(file_buffer):
try:
file_bytes = file_buffer.read()
pil_image = Image.open(io.BytesIO(file_bytes))
file_buffer.seek(0)
img_array = np.array(pil_image)
if len(img_array.shape) == 3:
img_bgr = cv2.cvtColor(img_array, cv2.COLOR_RGB2BGR)
else:
img_bgr = cv2.cvtColor(img_array, cv2.COLOR_GRAY2BGR)
return img_bgr
except Exception as e:
st.error(f"画像の読み込みに失敗しました: {str(e)}")
st.stop()
まとめ
- ファイルバッファ = ストリーム+しおり
- 本番環境ではI/Oの仕組みがローカルと違うため、不具合が出やすい
- bytes化 → seek(0) → 例外処理 の3ステップで安定
おわりに
今回学んだ「ファイルバッファ」という考え方は、PythonやStreamlitに限らず、Webアプリや他の言語でもよく登場する基本的な仕組みです。 自分は今回、ローカルでは動くのに本番で落ちたことで初めて気づきましたが、中身を知れば怖くないです。
同じようにハマった方の参考になれば嬉しいです。