Manipulação de arquivos: lendo e escrevendo texto

Trabalhar com arquivos de texto é uma das tarefas mais fundamentais na programação Python. Seja para processar logs, ler configurações, gerar relatórios ou persistir dados, dominar a leitura e escrita de arquivos é essencial. Neste artigo, você aprenderá desde a abertura segura de arquivos até boas práticas para evitar erros comuns.

1. Abrindo e fechando arquivos com open()

A função open() é a porta de entrada para manipular arquivos em Python. Ela recebe o caminho do arquivo e o modo de abertura como parâmetros principais.

Modos de abertura

Modo Descrição
'r' Leitura (padrão). O arquivo deve existir.
'w' Escrita. Cria um novo arquivo ou sobrescreve o existente.
'a' Append. Adiciona conteúdo ao final do arquivo.
'x' Criação exclusiva. Falha se o arquivo já existir.
'r+' Leitura e escrita (sem truncar).
'w+' Leitura e escrita (trunca o arquivo).

Gerenciador de contexto with

A forma mais segura e recomendada de trabalhar com arquivos é usando o gerenciador de contexto with:

with open('exemplo.txt', 'r', encoding='utf-8') as arquivo:
    conteudo = arquivo.read()
# O arquivo é fechado automaticamente ao sair do bloco

Sem o with, você precisaria chamar arquivo.close() manualmente, o que pode ser esquecido ou não executado em caso de exceções.

Encoding

Sempre especifique encoding='utf-8' ao abrir arquivos de texto. Isso evita problemas com caracteres acentuados e garante compatibilidade entre sistemas:

# Problema comum sem encoding
with open('dados.txt', 'r') as arquivo:  # Pode gerar UnicodeDecodeError
    dados = arquivo.read()

# Correto
with open('dados.txt', 'r', encoding='utf-8') as arquivo:
    dados = arquivo.read()

2. Lendo arquivos de texto

Python oferece várias formas de ler o conteúdo de um arquivo, cada uma adequada a diferentes cenários.

Leitura completa

# read() - lê todo o conteúdo como string única
with open('poema.txt', 'r', encoding='utf-8') as f:
    texto_completo = f.read()
    print(texto_completo)

# readlines() - retorna lista de linhas
with open('poema.txt', 'r', encoding='utf-8') as f:
    linhas = f.readlines()
    print(f"O poema tem {len(linhas)} linhas")

Leitura linha por linha (eficiente para arquivos grandes)

# Iteração direta - não carrega tudo na memória
with open('grande_arquivo.txt', 'r', encoding='utf-8') as f:
    for linha in f:
        # Processa cada linha individualmente
        if 'erro' in linha.lower():
            print(f"Erro encontrado: {linha.strip()}")

Leitura parcial com read(n)

with open('arquivo.txt', 'r', encoding='utf-8') as f:
    primeiros_100_caracteres = f.read(100)
    print(primeiros_100_caracteres)

3. Escrevendo em arquivos de texto

Escrevendo com write() e writelines()

# write() - escreve uma string
with open('saida.txt', 'w', encoding='utf-8') as f:
    f.write('Primeira linha\n')
    f.write('Segunda linha\n')

# writelines() - escreve uma lista de strings (sem adicionar \n automaticamente)
linhas = ['Linha 1\n', 'Linha 2\n', 'Linha 3\n']
with open('saida.txt', 'w', encoding='utf-8') as f:
    f.writelines(linhas)

Modo append ('a')

with open('log.txt', 'a', encoding='utf-8') as f:
    f.write(f'{datetime.now()}: Nova entrada no log\n')

Controle de quebras de linha

Em Windows, as quebras de linha são \r\n, enquanto em Unix/Linux/macOS são \n. Python gerencia isso automaticamente no modo texto, mas ao escrever explicitamente, use \n:

with open('dados.txt', 'w', encoding='utf-8') as f:
    f.write('linha1\nlinha2\nlinha3\n')  # Python converte para \r\n no Windows

4. Manipulando caminhos e diretórios

Usando pathlib (moderno e recomendado)

from pathlib import Path

# Caminhos relativos e absolutos
caminho = Path('dados') / 'arquivo.txt'
caminho_absoluto = Path.home() / 'documentos' / 'relatorio.txt'

# Verificando existência
if caminho.exists():
    print(f"{caminho} existe")

if caminho.is_file():
    print(f"{caminho} é um arquivo")

# Criando diretórios automaticamente
caminho.parent.mkdir(parents=True, exist_ok=True)

Usando os.path (tradicional)

import os

# Verificando existência
if os.path.exists('arquivo.txt'):
    print("Arquivo existe")

if os.path.isfile('arquivo.txt'):
    print("É um arquivo regular")

# Criando diretórios
os.makedirs('dados/subdados', exist_ok=True)

5. Tratamento de erros comuns em arquivos

import os

def ler_arquivo_seguro(caminho):
    try:
        with open(caminho, 'r', encoding='utf-8') as f:
            return f.read()
    except FileNotFoundError:
        print(f"Erro: Arquivo '{caminho}' não encontrado")
        return None
    except PermissionError:
        print(f"Erro: Sem permissão para ler '{caminho}'")
        return None
    except IsADirectoryError:
        print(f"Erro: '{caminho}' é um diretório, não um arquivo")
        return None

# Exemplo de uso
conteudo = ler_arquivo_seguro('config.txt')
if conteudo:
    print(conteudo)

Tratamento para criação exclusiva

try:
    with open('novo_arquivo.txt', 'x', encoding='utf-8') as f:
        f.write('Conteúdo inicial')
except FileExistsError:
    print("Arquivo já existe! Use modo 'w' para sobrescrever")

6. Boas práticas e dicas essenciais

Sempre use with

Nunca use open() sem o gerenciador de contexto. O padrão correto é:

# ✅ Correto
with open('arquivo.txt', 'r') as f:
    dados = f.read()

# ❌ Evite
f = open('arquivo.txt', 'r')
dados = f.read()
f.close()  # Fácil de esquecer

Controle de buffer com flush()

Para garantir que os dados sejam escritos imediatamente (útil em logs):

with open('log.txt', 'a', encoding='utf-8') as f:
    f.write('Dado crítico\n')
    f.flush()  # Força a escrita no disco

Leitura eficiente para arquivos grandes

Para arquivos muito grandes que não cabem na memória:

# Leitura em blocos de 1KB
def ler_em_partes(caminho, tamanho_bloco=1024):
    with open(caminho, 'r', encoding='utf-8') as f:
        while True:
            bloco = f.read(tamanho_bloco)
            if not bloco:
                break
            yield bloco

# Uso
for parte in ler_em_partes('gigante.txt'):
    processar(parte)  # Função que processa cada parte

Exemplo completo: processador de arquivo de configuração

from pathlib import Path

def processar_config(caminho_config):
    config_path = Path(caminho_config)

    # Verifica se o arquivo existe
    if not config_path.exists():
        print(f"Criando arquivo de configuração padrão: {config_path}")
        config_path.parent.mkdir(parents=True, exist_ok=True)

        with open(config_path, 'w', encoding='utf-8') as f:
            f.write("# Configurações do sistema\n")
            f.write("DEBUG=False\n")
            f.write("PORT=8080\n")
        return

    # Lê e processa o arquivo existente
    with open(config_path, 'r', encoding='utf-8') as f:
        for linha in f:
            linha = linha.strip()
            if linha and not linha.startswith('#'):
                chave, valor = linha.split('=', 1)
                print(f"{chave}: {valor}")

# Uso
processar_config('config/sistema.conf')

Dominar a manipulação de arquivos em Python é um passo fundamental para qualquer desenvolvedor. Com as técnicas apresentadas aqui, você estará preparado para lidar desde pequenos arquivos de configuração até grandes volumes de dados de forma segura e eficiente.

Referências