はじめに

フォントを軽量化するWebアプリを作った際に、サーバ側で3つのファイルをzipにしてダウンロードさせるということをしました。この時、サーバ側のストレージには保存できないのでファイルを保存せずに、データを送る必要があったので、そんなときの方法です。

Pythonでのストリーム

まず、ドキュメントはこちらです。

Pythonでストリームを扱うモジュールはioです。特に、ファイルをストレージに保存せずにインメモリで扱えるストリームには、文字列を扱うio.StringIOと、バイナリを扱うio.BytesIOの2種類があります。

ローカルで何かやる場合は、中間ファイルなども逐一保存して、デバッグなどに利用するかもしれませんが、本番環境で全て保存してたら、ストレージの容量がなくなってくので(特にフォントみたいに重めのデータや複数ファイルの場合)、インメモリのストリームをうまく活用していきましょう。

実際に使ってみる

テキストモードファイルのように扱う

まず、StringIOです。これを使うことで、普通の文字列を、openで開いたテキストファイルのように扱うことができるようになります。

実際のコードがこちらです。

from io import StringIO

with StringIO() as bs:
    bs.write('こんにちは世界\n')
    bs.write('さよなら世界')
    content = bs.getvalue()

print(type(content))
print(content)

これの出力結果はこちらです。

<class 'str'>
こんにちは世界
さよなら世界

ファイルストリームと同様にwriteを使い、文字列を書き込んでいくことができます。そして最終的な文字列を得るためにはgetvalueを使います。これにより得られるのは文字列(str)です。

バイナリモードファイルのように扱う

次に、BytesIOです。これを扱うと、様々なバイナリファイルをインメモリで扱うことができるようになります。

実際のコードがこちらです。動作の確認のため、最後に結局ファイルとして保存するとかいう本末転倒なことしてますが、これぐらいしかみて取れる確認方法がなかったので、許してください。

from io import BytesIO
from PIL import Image

with BytesIO() as bs:
    img = Image.new('L', (512, 512), 0)
    img.save(bs, 'png')
    content = bs.getvalue()

with open('img.png', mode='wb') as fs:
    fs.write(content)

画像とかが見てわかりやすいかなと思ったので、画像を保存してみました。BytesIOで作成したオブジェクトにPillowのsaveを使って、画像を保存します。ただ、この時点ではまだ、メモリ上に存在しています。

これを普通のファイルのように様々なライブラリ等に渡すこともできますが、今回は単純に保存するため、openwriteします。

すると同じディレクトリにimg.pngという真っ黒な画像の出来上がりです。

おわりに

中間ファイルを保存せずに扱えるのは便利ですね。

zipにするのはまたちょっとややこしいので、別で書こうと思います。