Библиотека io в Python для ввода и вывода
Введение в библиотеку io для ввода/вывода
Модуль io из стандартной библиотеки Python предоставляет инструменты для работы с потоками данных. Он используется как основа для встроенной функции open() и позволяет гибко управлять буферизацией, кодировками и типами данных (текстовые или бинарные).
Основное решение: StringIO и BytesIO
Как сохранить данные в памяти, имитируя файловый объект?
Классы io.StringIO и io.BytesIO позволяют работать с буферами в оперативной памяти как с файлами. Они поддерживают те же методы (read, write, seek, tell), что и обычные файловые объекты.
import io
# Текстовый буфер
buf = io.StringIO()
buf.write('Привет, мир!')
buf.seek(0)
content = buf.read()
print(content) # Привет, мир!
buf.close()
библиотека io python (библиотека io в python (ввод/вывод))
Привет, мир!
Аналогично для бинарных данных используется io.BytesIO:
binary_buf = io.BytesIO()
binary_buf.write(b'\x00\x01\x02')
binary_buf.seek(0)
data = binary_buf.read()
binary_buf.close()
Типичные ошибки:
- Запись текста в BytesIO (нужно передавать bytes). Решение: явное преобразование через
.encode(). - Чтение после записи без сброса позиции (seek). Решение: перед чтением вызвать
seek(0). - Незакрытие потока - хотя для коротких скриптов это не критично, лучше использовать менеджер контекста
with.
Вариант 1: Использование io.StringIO для обработки текста
Как обработать многострочный текст в памяти без создания временного файла?
import io
text = """Строка1
Строка2
Строка3"""
buf = io.StringIO(text)
for line in buf:
print(line.strip())
Строка1 Строка2 Строка3
Проблема: забытая новая строка в конце - read() возвращает весь текст, включая лишние символы. Решение: использовать rstrip().
Вариант 2: io.BytesIO для бинарных данных
Как записать изображение или сериализованный объект в байтовый массив?
import io
import json
data = {'key': 'value'}
buf = io.BytesIO()
buf.write(json.dumps(data).encode('utf-8'))
buf.seek(0)
result = json.loads(buf.read())
print(result)
{'key': 'value'}
Вариант 3: Буферизированные потоки (BufferedReader, BufferedWriter)
Как увеличить производительность при чтении/записи большого объёма данных?
Классы io.BufferedReader и io.BufferedWriter оборачивают низкоуровневый поток (например, FileIO) и добавляют буферизацию. Обычно они используются автоматически функцией open(), но можно создать вручную.
import io
# Низкоуровневый файловый объект
raw = io.FileIO('example.bin', 'wb')
buffered = io.BufferedWriter(raw, buffer_size=8192)
buffered.write(b'Hello' * 1000)
buffered.flush()
buffered.close()
Частая ошибка: забытый flush() - данные могут остаться в буфере. Решение: использование менеджера контекста, который автоматически сбрасывает буфер.
Вариант 4: TextIOWrapper для работы с текстом поверх бинарного потока
Как читать текстовый файл с нестандартной кодировкой или указать обработку ошибок?
io.TextIOWrapper оборачивает бинарный поток в текстовый с заданной кодировкой. Удобен, когда нужно вручную управлять буферизацией при текстовом вводе/выводе.
import io
raw = io.BytesIO(b'\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82')
text_wrapper = io.TextIOWrapper(raw, encoding='utf-8')
print(text_wrapper.read())
Привет
Ошибка: несоответствие кодировки. Решение: указать параметр encoding или использовать errors='replace'.
Вариант 5: Позиционирование в потоке (seek, tell)
Как перейти к определённому месту в данных и узнать текущую позицию?
Методы seek(offset, whence) и tell() работают одинаково для всех потоков. Параметр whence может быть io.SEEK_SET (0 – начало), io.SEEK_CUR (1 – текущая), io.SEEK_END (2 – конец).
import io
buf = io.BytesIO(b'0123456789')
buf.seek(5)
print(buf.read(3)) # b'567'
buf.seek(-2, io.SEEK_END)
print(buf.read()) # b'89'
b'567' b'89'
Расширенные примеры использования библиотеки io
Пример 1: Создание собственного потока на основе RawIOBase
Наследуясь от io.RawIOBase, можно реализовать поток, который читает данные из нестандартного источника (например, базы данных или генератора).
import io
class SimpleStream(io.RawIOBase):
def __init__(self, data):
self.data = data
self.pos = 0
def readable(self):
return True
def read(self, size=-1):
if size < 0:
rest = self.data[self.pos:]
self.pos = len(self.data)
return rest
else:
chunk = self.data[self.pos:self.pos+size]
self.pos += len(chunk)
return chunk
def readinto(self, b):
chunk = self.read(len(b))
b[:len(chunk)] = chunk
return len(chunk)
raw = SimpleStream(b'Testing raw stream')
buffered = io.BufferedReader(raw)
print(buffered.read(7))
print(buffered.read())
b'Testing' b' raw stream'
Пример 2: Работа с архивами в памяти (gzip + BytesIO)
Сжатие и распаковка данных напрямую в байтовом буфере без создания файлов.
import io
import gzip
# Запись сжатых данных
buf = io.BytesIO()
with gzip.GzipFile(fileobj=buf, mode='wb') as gz:
gz.write(b'Compressed content')
# Чтение сжатых данных
buf.seek(0)
with gzip.GzipFile(fileobj=buf, mode='rb') as gz:
decompressed = gz.read()
print(decompressed)
b'Compressed content'
Пример 3: Использование TextIOWrapper для обработки разных кодировок
Чтение данных, закодированных в CP1251, с заменой неподдерживаемых символов.
import io
raw = io.BytesIO(b'\xcf\xf0\xee\xe2\xe5\xf0\xea\xe0')
wrapper = io.TextIOWrapper(raw, encoding='cp1251', errors='replace')
print(wrapper.readline())
Проверка
Пример 4: Комбинация BufferedRandom для произвольного доступа
io.BufferedRandom позволяет одновременно читать и писать в бинарный поток.
import io
buf = io.BytesIO(b'Hello World')
buffered = io.BufferedRandom(buf)
buffered.seek(6)
buffered.write(b'Python')
buffered.seek(0)
print(buffered.read())
b'Hello Python'
Пример 5: Использование io.open для явного управления буферизацией
Функция io.open() позволяет задать размер буфера и политику кэширования.
import io
with io.open('test.txt', 'w', buffering=1024*1024) as f:
f.write('Large buffer test')
with io.open('test.txt', 'r', buffering=1) as f:
print(f.read())
Large buffer test
Пример 6: Поток с возможностью повторного чтения (io.BufferedReader с позицией)
Сброс позиции после чтения – использование seek(0) для повторного чтения.
import io
data = b'Repeatable'
buf = io.BytesIO(data)
buff = io.BufferedReader(buf)
print(buff.read())
buff.seek(0)
print(buff.read())
b'Repeatable' b'Repeatable'
Пример 7: Обработка ошибок кодировки при записи через StringIO
При записи в текстовый поток можно указать стратегию обработки ошибок (строгий режим, замена, игнорирование).
import io
# Пример с ошибкой – символ вне Latin-1
buf = io.StringIO()
try:
buf.write('\u2603') # снеговик вне Latin-1
# Если поток имеет кодировку ascii, произойдёт ошибка
# Но StringIO не привязан к кодировке, поэтому ошибки нет
except Exception as e:
print(f'Ошибка: {e}')
# Для BytesIO с TextIOWrapper можно задать errors
raw = io.BytesIO()
wrapper = io.TextIOWrapper(raw, encoding='ascii', errors='replace')
wrapper.write('\u2603')
wrapper.flush()
print(raw.getvalue())
b'?'
Пример 8: Поток с поддержкой seek на основе RandomAccessIO
Использование io.BytesIO для быстрого перемещения по большому объёму данных, например, для поиска подстроки.
import io
big_data = b'x' * 10000 + b'needle' + b'y' * 10000
buf = io.BytesIO(big_data)
# Поиск через чтение порциями
while True:
chunk = buf.read(1024)
if not chunk:
break
pos = chunk.find(b'needle')
if pos != -1:
print(f'Found at position {buf.tell() - len(chunk) + pos}')
break
Found at position 10000