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().







