Python BytesIOとは?メモリ上でバイナリを扱う方法

io.BytesIOは、メモリ上でバイナリファイルのように読み書きできるオブジェクトです。

画像やPDFをディスクに保存せずに処理できます。


BytesIOの基本

import io

b = io.BytesIO()
b.write(b"\x00\x01binary")

print(b.getvalue())

b.seek(0)
print(b.read(3))

実行結果:

b'\x00\x01binary'
b'\x00\x01b'

扱うのはbytesです。文字列は書き込めません。

io.BytesIO().write("abc")

実行結果:

TypeError: a bytes-like object is required, not 'str'

StringIOとの違い

StringIO BytesIO
扱う型 str bytes
対応するモード "r" / "w" "rb" / "wb"
用途 テキスト・CSV・JSON 画像・PDF・zip

実際のファイルで"rb"を使う場面ならBytesIOと覚えてください。


ダウンロードした画像を処理する

import io
import urllib.request
from PIL import Image

with urllib.request.urlopen(url, timeout=10) as r:
    data = r.read()

img = Image.open(io.BytesIO(data))
print(img.size)

これが最も多い使い方です。

Image.open()はファイルオブジェクトを受け取りますが、受信したデータはbytesです。BytesIOで包むとファイルとして渡せます。

一時ファイルを作る必要がありません。

  • ディスクに書かないので速い
  • 後片付けが不要
  • 書き込み権限がない環境でも動く

画像を保存せずに返す

import io
from PIL import Image

img = Image.new("RGB", (100, 100), "red")

buf = io.BytesIO()
img.save(buf, format="PNG")

data = buf.getvalue()
print(len(data), "bytes")
print(data[:8])

PNGのバイト列がメモリ上に作られます。

Webアプリで画像を生成して返すとき、この形でレスポンスに載せます。

buf.seek(0)
return send_file(buf, mimetype="image/png")

seek(0)を忘れると空のファイルが返ります。save()のあと位置が末尾にあるためです。

これは非常によくある不具合です。「画像が壊れている」「サイズが0」という症状が出たら、まずここを確認してください。


zipをメモリ上で作る

import io
import zipfile

buf = io.BytesIO()

with zipfile.ZipFile(buf, "w") as z:
    z.writestr("a.txt", "1つ目のファイル")
    z.writestr("b.txt", "2つ目のファイル")

print(len(buf.getvalue()), "bytes")

buf.seek(0)
with zipfile.ZipFile(buf) as z:
    print(z.namelist())
    print(z.read("a.txt").decode("utf-8"))

実行結果:

238 bytes
['a.txt', 'b.txt']
1つ目のファイル

ファイルを1つも作らずにzipを組み立てられました。

複数ファイルをまとめてダウンロードさせる機能などに使えます。


bytesとStringIOの変換

import io

data = "日本語テキスト".encode("utf-8")

text = io.BytesIO(data).read().decode("utf-8")
print(text)

実行結果:

日本語テキスト

テキストとして扱いたいだけなら、先にdecode()してStringIOに入れるほうが素直です。

s = io.StringIO(data.decode("utf-8"))

もう1つの方法として、TextIOWrapperで包むこともできます。

wrapper = io.TextIOWrapper(io.BytesIO(data), encoding="utf-8")
print(wrapper.read())

バイナリのストリームをテキストとして読むときに使います。巨大なデータを一度にdecode()したくない場合に有効です。


メモリを使う点に注意

BytesIOは全体をメモリに保持します。

数百MBのファイルを扱うと、そのぶんメモリを消費します。

データの大きさ 選ぶもの
数MB程度 BytesIO
大きい・不定 tempfile
順に処理できる ストリーム処理

大きなファイルは一時ファイルに書き出してください。「Python TemporaryFileとは?名前を持たない一時ファイルを使う方法」を参照してください。


BytesIOのまとめ

  • メモリ上でバイナリをファイルのように扱う
  • 扱うのはbytes——文字列は不可
  • ダウンロードした画像を保存せず処理できる
  • 書き込み後はseek(0)——忘れると空になる
  • zipもメモリ上で組み立てられる
  • 全体がメモリに載る——大きなデータには不向き

あわせて読みたい

プログラミング学習に興味がある方は、まずは無料体験会へ

無料体験会・お申し込みはこちら