Capítulo 8: Conteúdo Extra#

ch8

Tratamento de erros (try/except)#

Em Python, usamos o bloco try/except para lidar com erros que podem ocorrer durante a execução do programa, evitando que ele seja interrompido inesperadamente.

Exemplo básico com try/except:

try:
    numero = int(input("Digite um número: "))
    resultado = 10 / numero
    print(f"O resultado é {resultado}.")
except ZeroDivisionError:
    print("Erro: divisão por zero.")
except ValueError:
    print("Erro: entrada inválida.")

Saída:

Digite um número: 2
O resultado é 5.0.
Digite um número: 0
Erro: divisão por zero.
Digite um número: abc
Erro: entrada inválida.

Blocos else e finally no tratamento de erros#

Além do try e except, Python permite o uso opcional dos blocos else e finally para tornar o tratamento de erros mais completo.

else

Executado somente se nenhum erro ocorrer no bloco try.

finally

Executado sempre, com ou sem erro. É útil para liberar recursos, como fechar arquivos ou conexões com banco de dados.

Exemplo com else e finally:

try:
    numero = int(input("Digite um número: "))
    resultado = 10 / numero
except ZeroDivisionError:
    print("Erro: divisão por zero.")
except ValueError:
    print("Erro: entrada inválida.")
else:
    print(f"O resultado é {resultado}.")
finally:
    print("Execução finalizada.")

Saída:

Digite um número: 2
O resultado é 5.0.
Execução finalizada.
Digite um número: 0
Erro: divisão por zero.
Execução finalizada.
Digite um número: xyz
Erro: entrada inválida.
Execução finalizada.

Usando try/except em funções#

É comum usar try/except dentro de funções para torná-las mais seguras e reutilizáveis.

def dividir(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        return "Erro: divisão por zero."

print(dividir(10, 2))  # Saída: 5.0
print(dividir(10, 0))  # Saída: Erro: divisão por zero.

Saída:

5.0
Erro: divisão por zero.

Tabela com Resumo#

Bloco

Quando é executado

try

Tenta executar um código que pode dar erro

except

Executa se um erro ocorrer no try

else

Executa se nenhum erro ocorrer no try

finally

Sempre executa, com ou sem erro

Manipulação de arquivos#

Até agora, os dados dos nossos programas viviam apenas na memória: assim que o programa terminava, tudo se perdia. Para guardar informações de forma permanente (um relatório, uma lista de tarefas, configurações), gravamos os dados em arquivos.

Abrindo um arquivo: a função open()#

Para trabalhar com um arquivo, primeiro o abrimos com open(), informando o caminho e o modo de abertura:

arquivo = open("dados.txt", "r", encoding="utf-8")

O modo define o que podemos fazer com o arquivo:

Modo

Significado

Se o arquivo não existe

"r"

leitura (read)

gera erro

"w"

escrita (write), apaga o conteúdo anterior

cria um novo

"a"

acréscimo (append), escreve no final

cria um novo

"x"

criação exclusiva

cria; erro se já existir

"b"

modo binário (combina com os outros, ex.: "rb")

não se aplica

O parâmetro encoding="utf-8" garante que acentos e caracteres especiais sejam lidos e gravados corretamente.

O jeito certo: o bloco with#

Todo arquivo aberto precisa ser fechado para liberar os recursos do sistema. Em vez de chamar arquivo.close() na mão (e correr o risco de esquecer, ou de o programa falhar antes disso), usamos o bloco with, que fecha o arquivo automaticamente ao final, mesmo que ocorra um erro:

with open("zen.txt", "w", encoding="utf-8") as arquivo:
    arquivo.write("Bonito é melhor que feio.\n")
    arquivo.write("Explícito é melhor que implícito.\n")
    arquivo.write("Simples é melhor que complexo.\n")
# fora do with, o arquivo já está fechado

Boa prática - sempre use o with

Prefira sempre o with open(...) as ...:. Ele cuida do fechamento do arquivo para você, evitando vazamento de recursos e arquivos corrompidos. É o mesmo papel do bloco finally que vimos no try/except.

Lendo um arquivo#

Há algumas formas de ler o conteúdo de um arquivo aberto em modo leitura. A mais direta lê tudo de uma vez:

with open("zen.txt", "r", encoding="utf-8") as arquivo:
    conteudo = arquivo.read()      # lê o arquivo inteiro como uma única string
    print(conteudo)

Para arquivos grandes, ler linha a linha é mais eficiente. O próprio arquivo é iterável:

with open("zen.txt", "r", encoding="utf-8") as arquivo:
    for linha in arquivo:
        print(linha.strip())   # strip() remove o "\n" do final de cada linha

Saída:

Bonito é melhor que feio.
Explícito é melhor que implícito.
Simples é melhor que complexo.

Outros métodos úteis são readline() (lê uma linha por vez) e readlines() (devolve uma lista com todas as linhas).

Atenção - o modo de escrita apaga o conteúdo

Abrir um arquivo existente em modo "w" apaga todo o conteúdo anterior antes de escrever. Para adicionar informações sem perder o que já existe, use o modo "a" (append).

Juntando com o tratamento de erros#

Operações com arquivos falham por vários motivos (o arquivo não existe, falta de permissão). Por isso, é comum combinar arquivos com try/except:

try:
    with open("inexistente.txt", "r", encoding="utf-8") as arquivo:
        print(arquivo.read())
except FileNotFoundError:
    print("Erro: o arquivo não foi encontrado.")

Saída:

Erro: o arquivo não foi encontrado.

Dados do mundo real: datas, JSON e CSV#

Projetos reais lidam o tempo todo com datas, com dados estruturados (JSON) e com tabelas e planilhas (CSV). A biblioteca padrão do Python já traz módulos prontos para os três.

Datas e horas com datetime#

O módulo datetime representa datas e horários e permite fazer contas com eles:

from datetime import datetime, timedelta

agora = datetime.now()
print(agora)                         # ex.: 2024-05-04 14:30:00.123456

# Criando uma data específica (ano, mês, dia, hora, minuto)
estreia = datetime(1975, 4, 3, 20, 0)
print(estreia.year, estreia.month)   # 1975 4

Para exibir uma data em um formato legível usamos strftime(); para interpretar uma data escrita em texto usamos strptime():

agora = datetime.now()
print(agora.strftime("%d/%m/%Y às %H:%M"))   # ex.: 04/05/2024 às 14:30

texto = "03/04/1975"
data = datetime.strptime(texto, "%d/%m/%Y")
print(data.year)                              # 1975

A diferença entre duas datas vira um timedelta, com o qual fazemos aritmética de tempo:

estreia_na_tv = datetime(1969, 10, 5)   # primeiro episódio do Monty Python
hoje = datetime.now()
dias_passados = (hoje - estreia_na_tv).days
print(f"Já se passaram {dias_passados} dias.")

daqui_uma_semana = hoje + timedelta(days=7)

Dica - principais códigos de formatação

Os códigos mais comuns do strftime e do strptime são %d (dia), %m (mês), %Y (ano com 4 dígitos), %H (hora), %M (minuto) e %S (segundo).

JSON: salvando dados estruturados#

O JSON é o formato mais usado para troca de dados entre programas e na web. O módulo json converte entre dicionários e listas do Python e o texto no formato JSON:

import json

personagem = {
    "nome": "Rei Arthur",
    "filme": "Monty Python em Busca do Cálice Sagrado",
    "tem_cavalo": False,
    "cavaleiros": ["Lancelot", "Galahad", "Robin"],
}

# dict -> string JSON
texto_json = json.dumps(personagem, indent=2, ensure_ascii=False)
print(texto_json)

# string JSON -> dict
de_volta = json.loads(texto_json)
print(de_volta["nome"])   # Rei Arthur

Para ler e gravar direto em arquivos, use json.dump() e json.load():

# Grava o dicionário em um arquivo .json
with open("personagem.json", "w", encoding="utf-8") as f:
    json.dump(personagem, f, indent=2, ensure_ascii=False)

# Lê o arquivo de volta para um dicionário
with open("personagem.json", "r", encoding="utf-8") as f:
    dados = json.load(f)

Dica - JSON legível e com acentos

Use indent=2 para gerar um JSON identado (mais fácil de ler) e ensure_ascii=False para manter acentos e caracteres especiais em vez de códigos como á.

Repare no padrão: dumps e loads (com “s” de string) trabalham com texto, enquanto dump e load trabalham diretamente com arquivos.

CSV: tabelas e planilhas#

O CSV (Comma-Separated Values) guarda dados em forma de tabela, com uma linha por registro e os valores separados por vírgula. É o formato que planilhas como o Excel exportam. O módulo csv lê e escreve esses arquivos:

import csv

filmes = [
    ["titulo", "ano"],
    ["Em Busca do Cálice Sagrado", 1975],
    ["A Vida de Brian", 1979],
    ["O Sentido da Vida", 1983],
]

# Escrevendo (newline="" evita linhas em branco extras)
with open("filmes.csv", "w", newline="", encoding="utf-8") as f:
    escritor = csv.writer(f)
    escritor.writerows(filmes)

# Lendo de volta
with open("filmes.csv", "r", encoding="utf-8") as f:
    leitor = csv.reader(f)
    for linha in leitor:
        print(linha)

Saída:

['titulo', 'ano']
['Em Busca do Cálice Sagrado', '1975']
['A Vida de Brian', '1979']
['O Sentido da Vida', '1983']

Note que o CSV não guarda o tipo dos dados: o ano 1975 volta da leitura como a string '1975'. Quando cada coluna tem um nome, csv.DictReader e csv.DictWriter deixam o código mais claro, lendo cada linha como um dicionário.

Para ir além - a biblioteca pandas

Para análises mais sérias de tabelas (filtros, estatísticas, gráficos), a biblioteca pandas é o padrão da área. O módulo csv da biblioteca padrão é suficiente para tarefas simples e não exige instalação.

Módulos, pacotes e ambientes virtuais#

À medida que os programas crescem, deixamos de escrever tudo em um único arquivo. Organizamos o código em módulos e pacotes, e reaproveitamos bibliotecas escritas por outras pessoas.

Módulos: reaproveitando código#

Um módulo é simplesmente um arquivo .py. Já usamos vários módulos da biblioteca padrão, como o random e o datetime. Para usar um módulo, basta importá-lo:

import math

print(math.sqrt(16))    # 4.0
print(math.pi)          # 3.141592653589793

Podemos importar apenas o que precisamos, ou dar um apelido com as:

from math import sqrt, pi     # importa nomes específicos
import numpy as np            # apelido, comum em bibliotecas

Criando o seu próprio módulo#

Qualquer arquivo .py seu também é um módulo. Suponha um arquivo saudacoes.py:

# saudacoes.py
def ola(nome):
    return f"Olá, {nome}!"

PI = 3.14159

Em outro arquivo, na mesma pasta, você o importa pelo nome (sem o .py):

import saudacoes

print(saudacoes.ola("Arthur"))   # Olá, Arthur!

O idioma if __name__ == "__main__"#

Quando um arquivo pode ser tanto executado diretamente quanto importado por outro, protegemos o código de teste com if __name__ == "__main__":. Esse bloco só roda quando o arquivo é executado diretamente, e não quando ele é importado:

# saudacoes.py
def ola(nome):
    return f"Olá, {nome}!"

if __name__ == "__main__":
    # Só executa ao rodar: python saudacoes.py
    print(ola("mundo"))

Pacotes#

Um pacote é uma pasta que agrupa vários módulos relacionados. Na prática, é um diretório com arquivos .py (e, tradicionalmente, um arquivo __init__.py). Bibliotecas grandes como o NumPy e o Matplotlib são pacotes.

Instalando bibliotecas com o pip#

Nem tudo vem na biblioteca padrão. Milhares de bibliotecas de terceiros estão disponíveis no PyPI (Python Package Index) e são instaladas com o pip, o gerenciador de pacotes do Python. No terminal:

pip install requests

Depois de instalada, a biblioteca é usada como qualquer outra:

import requests

resposta = requests.get("https://api.github.com")
print(resposta.status_code)   # 200

Dica - pip no Colab e no Jupyter

Dentro de um notebook (como o Google Colab), execute o pip em uma célula prefixada com !, assim: !pip install requests. O ! indica que a linha é um comando de terminal, e não código Python.

Ambientes virtuais (venv)#

Projetos diferentes podem precisar de versões diferentes da mesma biblioteca. Para evitar conflitos, criamos um ambiente virtual: uma instalação isolada do Python e das suas bibliotecas, separada por projeto. O módulo venv faz isso. No terminal:

# Cria o ambiente em uma pasta chamada .venv
python -m venv .venv

# Ativa o ambiente
source .venv/bin/activate     # Linux e macOS
.venv\Scripts\activate        # Windows

# Com o ambiente ativo, o pip instala apenas nele
pip install requests

# Ao terminar, desative
deactivate

Boa prática - um ambiente por projeto

Crie um ambiente virtual para cada projeto e evite instalar tudo no Python do sistema. Assim, as dependências de um projeto não interferem nas de outro, e fica fácil recriar o ambiente em outra máquina (por exemplo, listando as bibliotecas em um arquivo requirements.txt).

Uma alternativa moderna: o uv#

Nos últimos anos, o uv se popularizou como um substituto rápido para o pip e o venv. Escrito em Rust pela Astral, ele é muito mais veloz e reúne, em um só comando, a criação de ambientes e a instalação de pacotes.

Instalação (uma única vez, no sistema):

# Instalador oficial (Linux e macOS)
curl -LsSf https://astral.sh/uv/install.sh | sh

# Ou, se você já tem o pip
pip install uv

No dia a dia, ele cria o ambiente e instala pacotes muito mais rápido que o pip:

# Cria o ambiente virtual (equivale ao python -m venv .venv)
uv venv

# Instala pacotes (interface compatível com o pip)
uv pip install requests

O uv também gerencia o projeto inteiro, controlando as dependências em um arquivo pyproject.toml:

uv init meu_projeto      # cria a estrutura do projeto
cd meu_projeto
uv add requests          # adiciona uma dependência (e atualiza o pyproject.toml)
uv run main.py           # roda o script já dentro do ambiente do projeto

Dica - por que o uv?

O uv faz o papel de venv e pip (e mais) sendo dezenas de vezes mais rápido. Os comandos uv venv e uv pip install são quase idênticos aos que você já conhece, então a transição é simples. Hoje é a ferramenta recomendada para novos projetos.

Para saber mais

Documentação oficial do pip e do PyPI: https://pypi.org/. A lista completa de módulos da biblioteca padrão (que não precisam de instalação) está em https://docs.python.org/3/library/. Documentação do uv: https://docs.astral.sh/uv/.