Python StringIOとは?文字列をファイルのように扱う方法
io.StringIOは、メモリ上でファイルのように読み書きできるオブジェクトです。
実際のファイルを作らずに、ファイル用の関数をそのまま使えます。
StringIOの基本
import io
s = io.StringIO()
s.write("1行目\n")
s.write("2行目\n")
print(repr(s.getvalue()))
実行結果:
'1行目\n2行目\n'
getvalue()で書き込んだ内容全体を取り出せます。
初期値を渡して読み取り用に使うこともできます。
s = io.StringIO("a\nb\nc\n")
print([line.rstrip() for line in s])
実行結果:
['a', 'b', 'c']
for文で1行ずつ回せます。ファイルとまったく同じ使い方です。
read()が空になる理由
s = io.StringIO()
s.write("1行目\n")
print(repr(s.read()))
実行結果:
''
書いた直後に読むと空です。
原因は読み書きの位置にあります。書き込み後、位置は末尾にあります。そこから読んでも何もありません。
s.seek(0)
print(repr(s.read()))
実行結果:
'1行目\n'
seek(0)で先頭に戻してから読みます。
これは実際のファイルでも同じ挙動です。getvalue()は位置に関係なく全体を返すため、書いた内容を取り出すだけならgetvalue()が簡単です。
ファイル用の関数をテストする
import io
def count_lines(f):
return sum(1 for _ in f)
print(count_lines(io.StringIO("x\ny\nz\n")))
実行結果:
3
ファイルを引数に取る関数を、ファイルなしでテストできます。
テスト用のファイルを用意する必要がなく、後片付けも不要です。
- 一時ファイルを作らない
- テストが並行実行しても衝突しない
- ディスクI/Oがないので速い
関数を書くときはファイル名ではなくファイルオブジェクトを受け取る設計にしておくと、テストしやすくなります。
csvと組み合わせる
import io
import csv
buf = io.StringIO()
w = csv.writer(buf)
w.writerow(["名前", "年齢"])
w.writerow(["田中", 30])
print(repr(buf.getvalue()))
実行結果:
'名前,年齢\r\n田中,30\r\n'
CSVを文字列として生成できます。
Webアプリでダウンロード用のCSVを返すときなど、ファイルに保存せず直接送りたい場面で使います。
読み込みも同様です。
r = csv.reader(io.StringIO("名前,年齢\n田中,30\n"))
print(list(r))
実行結果:
[['名前', '年齢'], ['田中', '30']]
APIから受け取ったCSV文字列を、保存せずにそのまま解析できます。
「Python csv.reader()とは?CSVを1行ずつ読み込む方法」も参照してください。
標準出力を捕まえる
import io
import sys
buf = io.StringIO()
old = sys.stdout
sys.stdout = buf
print("この出力は捕まえられる")
sys.stdout = old
print("捕まえた:", repr(buf.getvalue()))
実行結果:
捕まえた: 'この出力は捕まえられる\n'
print()の出力をテストできます。
ただし戻し忘れると以降の出力がすべて消えます。標準ライブラリに安全な書き方が用意されています。
import contextlib
import io
buf = io.StringIO()
with contextlib.redirect_stdout(buf):
print("捕まえたい出力")
print(repr(buf.getvalue()))
withを抜ければ自動で元に戻ります。例外が起きても確実です。
「Python contextlibとは?with文を自作する方法を解説」を参照してください。
closeすると取り出せない
s = io.StringIO("x")
s.close()
s.getvalue()
実行結果:
ValueError: I/O operation on closed file
getvalue()はclose()の前に呼んでください。
withで囲むと、抜けた時点で閉じられます。
with io.StringIO() as s:
s.write("data")
value = s.getvalue() # with の中で取り出す
実際のファイルと違い、StringIOは明示的に閉じなくても問題ありません。メモリ上のオブジェクトなので、参照がなくなれば解放されます。
bytesは扱えない
io.StringIO().write(b"abc")
実行結果:
TypeError: string argument expected, got 'bytes'
バイナリを扱うならBytesIOを使います。
「Python BytesIOとは?メモリ上でバイナリを扱う方法」で解説しています。
StringIOのまとめ
- メモリ上でファイルのように読み書きできる
- 書いた内容は
getvalue()で取り出す read()が空ならseek(0)を忘れている- ファイル用の関数をテストしやすくなる
- 標準出力の差し替えは
redirect_stdout()が安全 - bytesは扱えない——
BytesIOを使う




