io no Python: domine streams e buffers

Publicado em: 24/08/2026
Tempo de leitura: 7 minutos
Serene stream flowing through Bavarian mountains, capturing winter beauty and natural tranquility.

O módulo io define as principais interfaces de entrada e saída do Python. Arquivos abertos com open(), buffers em memória, wrappers de texto e vários objetos fornecidos por sockets, compressão e bibliotecas externas seguem contratos baseados nesse módulo.

Existem três categorias principais: I/O de texto, I/O binário com buffering e I/O bruto. Entender a diferença evita erros de tipo, perda de dados por encoding, escritas parciais, uso excessivo de memória e comportamentos inconsistentes entre plataformas.

Stream e file-like object

Um stream é uma sequência de dados acessada por operações como read(), write(), seek() e close(). Ele pode representar um arquivo, memória, pipe, socket, resposta HTTP, arquivo compactado ou implementação customizada.

Nem todo stream suporta todas as operações. Use readable(), writable() e seekable() para consultar capacidades. Uma operação não suportada pode gerar io.UnsupportedOperation.

Texto e bytes são contratos diferentes

Streams de texto recebem e produzem str. Streams binários recebem objetos bytes-like e produzem bytes. Misturar os tipos gera TypeError.

with open("dados.txt", "w", encoding="utf-8") as arquivo:
    arquivo.write("Olá")

with open("imagem.png", "wb") as arquivo:
    arquivo.write(b"\x89PNG")

Não use modo texto para imagens, ZIPs, PDFs ou protocolos binários. Não use modo binário para texto sem definir explicitamente como ocorrerão encoding e decoding.

Sempre informe o encoding

O encoding padrão de open() depende da locale, exceto em UTF-8 Mode. Um arquivo que funciona em Linux pode falhar em Windows se o código omitir encoding="utf-8".

with open("README.md", "r", encoding="utf-8") as arquivo:
    conteudo = arquivo.read()

Para usar intencionalmente o encoding da locale, passe encoding="locale". Para novas APIs, UTF-8 explícito costuma ser a escolha mais previsível. O guia de codecs no Python detalha encodings e handlers de erro.

EncodingWarning

O Python pode avisar quando uma API usa o encoding padrão. Execute com -X warn_default_encoding ou defina PYTHONWARNDEFAULTENCODING. Funções que recebem encoding=None podem usar io.text_encoding() para emitir o warning no chamador.

import io


def ler_texto(caminho, encoding=None):
    encoding = io.text_encoding(encoding)
    with open(caminho, encoding=encoding) as arquivo:
        return arquivo.read()

Newlines

O argumento newline controla tradução de fins de linha. Com None, o modo universal reconhece \n, \r e \r\n e retorna \n. Com string vazia, os finais são reconhecidos mas preservados. Um valor específico restringe o terminador.

Para formatos que exigem bytes exatos, como protocolos, hashes e assinaturas, use modo binário ou escolha explicitamente newline="" conforme a API.

Context manager e fechamento

Objetos derivados de IOBase suportam with. O fechamento ocorre mesmo se uma exceção for levantada.

with open("relatorio.txt", "w", encoding="utf-8") as arquivo:
    arquivo.write("resultado\n")

Depois de fechado, operações normalmente geram ValueError. Chamar close() mais de uma vez é permitido, mas não use o objeto novamente.

flush() não é fsync()

flush() envia dados do buffer Python para a camada subjacente. Isso não garante persistência física em disco. Quando durabilidade é necessária, faça flush e depois use os.fsync()` no descritor, entendendo o custo e as garantias da plataforma.

import os

with open("estado.txt", "w", encoding="utf-8") as arquivo:
    arquivo.write("confirmado")
    arquivo.flush()
    os.fsync(arquivo.fileno())

Para atualização atômica, escreva em arquivo temporário no mesmo filesystem e substitua o destino. Veja tempfile no Python.

I/O bruto

RawIOBase representa acesso de baixo nível a bytes. FileIO é a implementação de arquivos do sistema. Operações brutas podem retornar menos bytes que o solicitado ou escrever apenas parte do buffer.

import io

bruto = io.FileIO("dados.bin", "w")
try:
    dados = memoryview(b"conteudo")
    while dados:
        quantidade = bruto.write(dados)
        if quantidade is None:
            continue
        dados = dados[quantidade:]
finally:
    bruto.close()

Na maioria das aplicações, prefira streams buffered, que repetem operações quando apropriado e entregam uma interface mais previsível.

BufferedReader e BufferedWriter

BufferedReader lê blocos maiores da camada bruta e mantém bytes para chamadas seguintes. BufferedWriter acumula dados e os envia ao raw stream quando o buffer enche, em flush(), seek ou fechamento.

O tamanho padrão está em io.DEFAULT_BUFFER_SIZE, embora open() possa considerar o block size do arquivo. Não aumente buffers indiscriminadamente: throughput, memória e latência precisam ser medidos.

read(), read1() e readinto()

read(size) pode realizar várias chamadas ao raw stream. read1(size) tenta no máximo uma leitura bruta. readinto(buffer) reutiliza memória já alocada.

buffer = bytearray(64 * 1024)
with open("grande.bin", "rb") as arquivo:
    while quantidade := arquivo.readinto(buffer):
        processar(memoryview(buffer)[:quantidade])

Esse padrão reduz alocações em pipelines de alto volume. Não retenha o memoryview além da próxima sobrescrita do buffer sem fazer uma cópia.

BytesIO

BytesIO fornece um stream binário em memória.

import io

stream = io.BytesIO()
stream.write(b"cabecalho")
stream.seek(0)
print(stream.read())

getvalue() retorna uma cópia dos bytes. getbuffer() retorna uma view sem cópia e permite modificar o conteúdo. Enquanto a view existir, o BytesIO não pode ser redimensionado nem fechado.

StringIO

StringIO é um stream de texto Unicode em memória. É útil para testes, geração de relatórios e captura de saída.

import io

saida = io.StringIO()
print("primeira linha", file=saida)
print("segunda linha", file=saida)
texto = saida.getvalue()

Para simular append, posicione no final com seek(0, io.SEEK_END). Ao fechar, o buffer é descartado e getvalue() deixa de funcionar.

TextIOWrapper

TextIOWrapper transforma um stream binário buffered em stream de texto. Ele gerencia encoding, errors e newlines.

import io

bruto = open("dados.txt", "rb", buffering=0)
buffer = io.BufferedReader(bruto)
texto = io.TextIOWrapper(buffer, encoding="utf-8", errors="strict")
try:
    print(texto.readline())
finally:
    texto.close()

Na prática, open(..., encoding=...) constrói essas camadas automaticamente.

Políticas de erro de encoding

errors="strict" gera exceção e deve ser padrão para dados que precisam de integridade. ignore apaga dados silenciosamente e raramente é adequado. replace insere marcador. backslashreplace, xmlcharrefreplace e namereplace servem a contextos específicos.

Não escolha ignore apenas para “fazer o arquivo abrir”. Corrija o encoding da origem ou registre uma política de substituição explícita.

reconfigure()

TextIOWrapper.reconfigure() altera encoding, errors, newline, line buffering e write-through. Depois de uma leitura, encoding e newline não podem ser mudados, porque o decoder já possui estado.

import sys

sys.stdout.reconfigure(encoding="utf-8", errors="backslashreplace")

Alterar streams globais afeta toda a aplicação. Faça isso apenas na inicialização e respeite ambientes que redirecionam stdout.

seek() e tell() em texto

Em streams binários, posições representam offsets de bytes. Em TextIOWrapper, tell() retorna um cookie opaco que inclui estado do decoder. Use apenas valores retornados por tell() em seek(cookie).

Não some ou subtraia números arbitrariamente em posição textual. Para acesso aleatório por bytes, trabalhe no stream binário e decodifique blocos com cuidado.

Streams não bloqueantes

Em streams raw não bloqueantes, read() pode retornar None e write() pode escrever parcialmente. Streams buffered podem gerar BlockingIOError. Text I/O sobre descritores não bloqueantes também pode levantar essa exceção.

O artigo de select no Python explica como esperar prontidão antes de repetir.

detach()

detach() separa a camada subjacente de um buffer ou wrapper. Depois disso, o objeto exterior fica inutilizável.

buffer_binario = texto.detach()

Use apenas quando a transferência de ownership está clara. StringIO e BytesIO não possuem uma camada subjacente destacável.

closefd e descritores existentes

Ao criar FileIO ou usar open() com file descriptor inteiro, closefd=False impede que fechar o stream feche o descriptor. Essa escolha exige ownership explícito para evitar vazamento ou double close.

opener personalizado

O argumento opener permite controlar flags passadas ao sistema, por exemplo para abrir um caminho relativo a um diretório seguro.

import os

raiz_fd = os.open("dados", os.O_RDONLY)
try:
    def opener(path, flags):
        return os.open(path, flags, dir_fd=raiz_fd)

    with open("arquivo.txt", "r", encoding="utf-8", opener=opener) as arquivo:
        print(arquivo.read())
finally:
    os.close(raiz_fd)

Valide nomes e evite path traversal. O opener não corrige automaticamente uma política de caminhos insegura.

Compressão e file-like objects

Muitos módulos aceitam file-like objects em vez de paths. Isso permite empilhar transformações.

import gzip
import io

origem = io.BytesIO(dados_compactados)
with gzip.GzipFile(fileobj=origem, mode="rb") as arquivo:
    conteudo = arquivo.read(1_000_000)

Mesmo em memória, imponha limites de expansão. Veja gzip no Python.

Protocolos Reader e Writer no Python 3.14

O Python 3.14 adiciona io.Reader[T] e io.Writer[T] para tipar funções que precisam apenas de read() ou write(), sem exigir uma classe concreta.

from io import Reader, Writer


def copiar_texto(origem: Reader[str], destino: Writer[str]) -> None:
    while bloco := origem.read(8192):
        destino.write(bloco)

Essa tipagem estrutural aceita arquivos, StringIO e implementações customizadas com o contrato correto.

Thread safety e reentrância

FileIO acompanha as garantias das syscalls subjacentes. Streams binários buffered protegem estruturas internas com lock e podem ser usados por múltiplas threads. TextIOWrapper não é thread-safe.

Objetos buffered não são reentrantes. Fazer I/O no mesmo stream a partir de um signal handler pode gerar RuntimeError. O conjunto sobre signal no Python recomenda handlers mínimos sem logging ou escrita complexa.

Performance

Buffered I/O oferece desempenho previsível e normalmente deve ser preferido ao raw I/O. Text I/O acrescenta custo de codec. Para arquivos gigantes, leia em blocos, processe incrementalmente e evite read() sem limite.

StringIO e BytesIO são eficientes para dados moderados, mas ainda armazenam tudo em RAM. Para volumes grandes, use arquivo temporário ou streaming.

Testes recomendados

Teste texto e bytes, Unicode, encoding inválido, newlines, arquivo vazio, leitura parcial, escrita parcial em raw stream, non-blocking, seek/tell, fechamento duplicado, detach, BytesIO com view ativa, StringIO, ownership de file descriptor e limites de memória.

Erros comuns

Os erros mais frequentes são omitir encoding, misturar str e bytes, usar errors="ignore", presumir escrita completa em raw I/O, ler arquivos enormes de uma vez, confundir flush com persistência, calcular offsets em stream textual e fechar um descritor que pertence a outra camada.

Conclusão

io fornece os contratos centrais de streams no Python. Texto, bytes, raw I/O, buffering e objetos em memória são camadas diferentes que podem ser combinadas de forma explícita.

Informe encoding, prefira buffering, valide retornos de raw streams, use context managers e imponha limites. Consulte a documentação oficial de io e o guia oficial da função open().

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Business professional analyzing financial data on multiple computer monitors at his workspace.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    select no Python: monitore vários I/Os

    Aprenda select no Python para monitorar sockets e pipes, tratar leituras parciais, backpressure, epoll, poll e sinais sem busy loop.

    Ler mais

    Tempo de leitura: 7 minutos
    24/08/2026
    View of multiple railway tracks with signals and buildings in an urban setting during daytime.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    signal no Python: encerre processos bem

    Aprenda signal no Python para tratar SIGTERM e SIGINT, encerrar serviços, usar timers, wakeup FD e coordenar shutdown sem deadlocks.

    Ler mais

    Tempo de leitura: 7 minutos
    24/08/2026
    Detailed view of programming code in a dark theme on a computer screen.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    errno no Python: entenda erros do sistema

    Aprenda errno no Python para interpretar códigos do sistema, tratar OSError, rede, arquivos, retries e chamadas nativas de forma portátil.

    Ler mais

    Tempo de leitura: 6 minutos
    24/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    ctypes no Python: use bibliotecas C

    Aprenda ctypes no Python para carregar bibliotecas C, definir tipos e ponteiros, gerenciar memória, callbacks, ABI e erros com segurança.

    Ler mais

    Tempo de leitura: 6 minutos
    24/08/2026
    A laptop screen showing a code editor with visible programming code in a dimly lit environment.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    expat no Python: parser XML de baixo nível

    Aprenda expat no Python para parsing XML de baixo nível, handlers, namespaces, erros e proteções contra amplificação e DoS.

    Ler mais

    Tempo de leitura: 5 minutos
    23/08/2026
    A laptop screen showing a code editor with visible programming code in a dimly lit environment.
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    ElementInclude no Python: use XInclude

    Aprenda ElementInclude no Python para usar XInclude com loaders seguros, base URL, profundidade máxima e bloqueio de caminhos externos.

    Ler mais

    Tempo de leitura: 5 minutos
    23/08/2026