StringIO 与 BytesIO
本教程共 70 篇 · 第 46 篇 · 更新于 2026-07-22 · 约 4 分钟阅读
46. StringIO 与 BytesIO
本节目标:理解内存文件的概念,学会用
StringIO和BytesIO在内存中读写数据,掌握它们的典型使用场景。
很多时候你需要「像操作文件一样读写数据」,但数据其实不需要存到磁盘上。比如在单元测试里模拟文件内容,或者在网络程序里暂存收到的数据。
StringIO 和 BytesIO 提供了内存中的「伪文件」对象。它们有和真实文件一样的 read()、write() 方法,但数据完全存在于内存中。
StringIO:内存中的文本文件
from io import StringIO
# 写入
output = StringIO()
output.write('第一行\n')
output.write('第二行\n')
# 获取全部内容
content = output.getvalue()
print(content)
output.close()
StringIO 的行为和文本模式的文件对象几乎一样:
from io import StringIO
# 用字符串初始化
input_stream = StringIO('Hello\nWorld\n')
for line in input_stream:
print(line.strip())
input_stream.close()
Tip
StringIO处理的是str(文本),不是bytes。如果你需要处理二进制数据,用BytesIO。
BytesIO:内存中的二进制文件
from io import BytesIO
# 写入二进制数据
output = BytesIO()
output.write(b'\x89PNG\r\n\x1a\n')
# 获取 bytes
data = output.getvalue()
print(data)
output.close()
BytesIO 和 StringIO 的 API 完全一致,区别只在于前者操作 bytes,后者操作 str。
为什么不用普通字符串/列表
你可能会问:直接在字符串上拼接不就行了吗?为什么需要 StringIO?
原因有两点:
- 接口统一:很多函数接受「文件对象」作为参数。用
StringIO可以让这些函数处理内存数据,不需要改动接口。 - 性能:频繁字符串拼接会创建大量临时对象。
StringIO内部用列表缓冲,最后一次性拼接,效率更高。
# 反例:频繁拼接
result = ''
for i in range(10000):
result += f"line {i}\n"
# 正例:用 StringIO
from io import StringIO
buf = StringIO()
for i in range(10000):
buf.write(f"line {i}\n")
result = buf.getvalue()
Note对于超大规模数据,直接用列表
append再join性能最好。StringIO的优势在于「兼容文件接口」,不只是性能。
典型场景
场景一:单元测试模拟文件
测试一个解析 CSV 的函数,不需要真的创建临时文件:
from io import StringIO
def parse_csv(file_obj):
result = []
for line in file_obj:
result.append(line.strip().split(','))
return result
# 测试
csv_data = "name,age\nAlice,25\nBob,30\n"
fake_file = StringIO(csv_data)
result = parse_csv(fake_file)
assert result[1] == ['Alice', '25']
场景二:捕获 print 输出
验证某个函数是否正确打印了日志:
from io import StringIO
import sys
def log_message(msg):
print(f"[LOG] {msg}")
# 重定向 stdout
old_stdout = sys.stdout
sys.stdout = captured = StringIO()
log_message("系统启动")
sys.stdout = old_stdout
output = captured.getvalue()
assert "系统启动" in output
Warning重定向
sys.stdout是全局操作,多线程环境下会有竞态问题。更安全的做法是给被测函数加一个file参数,让它支持传入任意文件对象。
场景三:网络数据暂存
收到一段二进制数据,需要像读文件一样解析:
from io import BytesIO
def parse_header(data: bytes):
stream = BytesIO(data)
magic = stream.read(4)
version = int.from_bytes(stream.read(2), 'big')
return magic, version
raw = b'\x89PNG\r\n\x1a\n\x00\x01'
magic, version = parse_header(raw)
print(magic, version)
场景四:图片处理中间缓冲
from io import BytesIO
from PIL import Image
# 把内存中的 bytes 当成图片打开
img_data = download_image() # 假设返回图片的 bytes
img = Image.open(BytesIO(img_data))
img.thumbnail((100, 100))
# 处理后写回内存
output = BytesIO()
img.save(output, format='PNG')
processed_data = output.getvalue()
与 with 语句配合
StringIO 和 BytesIO 也支持上下文管理器:
from io import StringIO
with StringIO() as buf:
buf.write('line 1\n')
buf.write('line 2\n')
content = buf.getvalue()
print(content)
不过它们没有真正的系统资源需要释放,close() 只是释放内部缓冲区。用不用 with 差别不大,但保持统一风格总是好的。
小结
StringIO在内存中模拟文本文件,BytesIO模拟二进制文件。- 核心方法:
write()、read()、getvalue()、seek()、tell()。 - 主要价值是「兼容文件接口」和「避免频繁字符串拼接」。
- 单元测试、网络数据解析、图片处理是典型应用场景。
磁盘 I/O 是程序里最慢的操作之一。把数据留在内存里处理,能显著提升性能。StringIO 和 BytesIO 就是让你在不改动「文件式」代码的前提下,享受内存的速度。
来源:参考了 runoob「Python StringIO 模块」、liaoxuefeng「StringIO 和 BytesIO」等,改写后所得。