Tratamento de Exceções e Logging

Beginner

This tutorial is from open-source community. Access the source code

Introdução

Neste laboratório, você aprenderá como implementar o tratamento de exceções em Python. Você entenderá como usar o módulo logging para uma melhor comunicação de erros, o que é crucial para depurar e manter seu código.

Você também praticará a modificação do arquivo reader.py para lidar com erros de forma elegante, em vez de travar. Essa experiência prática aprimorará sua capacidade de escrever programas Python robustos.

Compreendendo Exceções em Python

Nesta etapa, vamos aprender sobre exceções em Python. Exceções são um conceito importante em programação. Elas nos ajudam a lidar com situações inesperadas que podem ocorrer enquanto um programa está sendo executado. Também vamos descobrir por que o código atual trava quando tenta processar dados inválidos. Compreender isso o ajudará a escrever programas Python mais robustos e confiáveis.

O que são Exceções?

Em Python, exceções são eventos que acontecem durante a execução de um programa e interrompem o fluxo normal de instruções. Pense nisso como um bloqueio em uma rodovia. Quando tudo corre bem, seu programa segue um caminho definido, assim como um carro em uma estrada livre. Mas quando um erro ocorre, o Python cria um objeto de exceção. Esse objeto é como um relatório que contém informações sobre o que deu errado, como o tipo de erro e onde ele aconteceu no código.

Se essas exceções não forem tratadas adequadamente, elas farão com que o programa trave. Quando uma falha ocorre, o Python mostra uma mensagem de traceback (rastreamento). Essa mensagem é como um mapa que mostra a localização exata no código onde o erro ocorreu. É muito útil para depuração.

Examinando o Código Atual

Vamos primeiro dar uma olhada na estrutura do arquivo reader.py. Este arquivo contém funções que são usadas para ler e converter dados CSV. Para abrir o arquivo no editor, precisamos navegar para o diretório correto. Usaremos o comando cd no terminal.

cd /home/labex/project

Agora que estamos no diretório certo, vamos ver o conteúdo de reader.py. Este arquivo tem várias funções importantes:

  1. convert_csv(): Esta função recebe linhas de dados e usa uma função conversora fornecida para convertê-las. É como uma máquina que pega matérias-primas (linhas de dados) e as transforma em uma forma diferente de acordo com uma receita específica (a função conversora).
  2. csv_as_dicts(): Esta função lê dados CSV e os transforma em uma lista de dicionários. Ela também realiza a conversão de tipos, o que significa que garante que cada dado no dicionário seja do tipo correto, como uma string, um inteiro ou um float.
  3. read_csv_as_dicts(): Esta é uma função wrapper (envoltório). É como um gerente que chama a função csv_as_dicts() para fazer o trabalho.

Demonstrando o Problema

Vamos ver o que acontece quando o código tenta processar dados inválidos. Vamos abrir um interpretador Python, que é como um playground onde podemos testar nosso código Python interativamente. Para abrir o interpretador Python, usaremos o seguinte comando no terminal:

python3

Depois que o interpretador Python estiver aberto, tentaremos ler o arquivo missing.csv. Este arquivo contém alguns dados ausentes ou inválidos. Usaremos a função read_csv_as_dicts() do arquivo reader.py para ler os dados.

from reader import read_csv_as_dicts
port = read_csv_as_dicts('missing.csv', types=[str, int, float])

Quando você executar este código, deverá ver uma mensagem de erro como esta:

Traceback (most recent call last):
  ...
ValueError: invalid literal for int() with base 10: ''

Este erro ocorre porque o código tenta converter uma string vazia em um inteiro. Uma string vazia não representa um inteiro válido, então o Python não pode fazer a conversão. A função trava no primeiro erro que encontra e para de processar o restante dos dados válidos no arquivo.

Para sair do interpretador Python, digite o seguinte comando:

exit()

Compreendendo o Fluxo de Erro

O erro acontece na função convert_csv(), especificamente na seguinte linha:

return list(map(lambda row: converter(headers, row), rows))

A função map() aplica a função converter a cada linha na lista rows. A função converter tenta aplicar os tipos (str, int, float) a cada linha. Mas quando encontra uma linha com dados ausentes, ela falha. A função map() não tem uma maneira integrada de lidar com exceções. Então, quando uma exceção ocorre, todo o processo trava.

Na próxima etapa, você modificará o código para lidar com essas exceções de forma elegante. Isso significa que, em vez de travar, o programa será capaz de lidar com os erros e continuar processando o restante dos dados.

Implementando o Tratamento de Exceções

Nesta etapa, vamos nos concentrar em tornar seu código mais robusto. Quando um programa encontra dados ruins, ele geralmente trava. Mas podemos usar uma técnica chamada tratamento de exceções para lidar com esses problemas de forma elegante. Você modificará o arquivo reader.py para implementar isso. O tratamento de exceções permite que seu programa continue sendo executado mesmo quando enfrenta dados inesperados, em vez de parar abruptamente.

Compreendendo os Blocos Try-Except

Python oferece uma maneira poderosa de lidar com exceções usando blocos try-except. Vamos detalhar como eles funcionam.

try:
    ## Código que pode causar uma exceção
    result = risky_operation()
except SomeExceptionType as e:
    ## Código que é executado se a exceção ocorrer
    handle_exception(e)

No bloco try, você coloca o código que pode gerar uma exceção. Uma exceção é um erro que ocorre durante a execução de um programa. Por exemplo, se você tentar dividir um número por zero, o Python gerará uma exceção ZeroDivisionError. Quando uma exceção ocorre no bloco try, o Python para de executar o código no bloco try e pula para o bloco except correspondente. O bloco except contém o código que tratará a exceção. O SomeExceptionType é o tipo de exceção que você deseja capturar. Você pode capturar tipos específicos de exceções ou usar um Exception geral para capturar todos os tipos de exceções. A parte as e permite que você acesse o objeto de exceção, que contém informações sobre o erro.

Modificando o Código

Agora, vamos aplicar o que aprendemos sobre blocos try-except à função convert_csv(). Abra o arquivo reader.py em seu editor.

  1. Substitua a função convert_csv() atual pelo seguinte código:
def convert_csv(rows, converter, header=True):
    """
    Convert a sequence of rows to an output sequence according to a conversion function.
    """
    if header:
        headers = next(rows)
    else:
        headers = []

    result = []
    for row_idx, row in enumerate(rows, start=1):
        try:
            ## Try to convert the row
            result.append(converter(headers, row))
        except Exception as e:
            ## Print a warning message for bad rows
            print(f"Row {row_idx}: Bad row: {row}")
            continue

    return result

Nesta nova implementação:

  • Usamos um loop for em vez de map() para processar cada linha. Isso nos dá mais controle sobre o processamento de cada linha.
  • Envolvemos o código de conversão em um bloco try-except. Isso significa que, se uma exceção ocorrer durante a conversão de uma linha, o programa não travará. Em vez disso, ele pulará para o bloco except.
  • No bloco except, imprimimos uma mensagem de erro para linhas inválidas. Isso nos ajuda a identificar quais linhas têm problemas.
  • Depois de imprimir a mensagem de erro, usamos a instrução continue para pular a linha atual e continuar processando as linhas restantes.

Salve o arquivo após fazer essas alterações.

Testando suas Mudanças

Vamos testar seu código modificado com o arquivo missing.csv. Primeiro, abra o interpretador Python executando o seguinte comando em seu terminal:

python3

Depois de estar no interpretador Python, execute o seguinte código:

from reader import read_csv_as_dicts
port = read_csv_as_dicts('missing.csv', types=[str, int, float])
print(f"Number of valid rows processed: {len(port)}")

Quando você executar este código, deverá ver mensagens de erro para cada linha problemática. Mas o programa continuará processando e retornará as linhas válidas. Aqui está um exemplo do que você pode ver:

Row 4: Bad row: ['C', '', '53.08']
Row 7: Bad row: ['DIS', '50', 'N/A']
Row 8: Bad row: ['GE', '', '37.23']
Row 13: Bad row: ['INTC', '', '21.84']
Row 17: Bad row: ['MCD', '', '51.11']
Row 19: Bad row: ['MO', '', '70.09']
Row 22: Bad row: ['PFE', '', '26.40']
Row 26: Bad row: ['VZ', '', '42.92']
Number of valid rows processed: 20

Vamos também verificar se o programa funciona corretamente com dados válidos. Execute o seguinte código no interpretador Python:

valid_port = read_csv_as_dicts('valid.csv', types=[str, int, float])
print(f"Number of valid rows processed: {len(valid_port)}")

Você deve ver que todas as linhas são processadas sem erros. Aqui está um exemplo da saída:

Number of valid rows processed: 17

Para sair do interpretador Python, execute o seguinte comando:

exit()

Agora seu código é mais robusto. Ele pode lidar com dados inválidos de forma elegante, ignorando linhas ruins em vez de travar. Isso torna seu programa mais confiável e fácil de usar.

Implementando o Logging

Nesta etapa, vamos melhorar seu código. Em vez de usar mensagens de impressão simples, usaremos o módulo logging do Python para um logging (registro) adequado. O logging é uma ótima maneira de acompanhar o que seu programa está fazendo, especialmente quando se trata de lidar com erros e entender o fluxo do seu código.

Compreendendo o Módulo Logging

O módulo logging em Python nos dá uma maneira flexível de enviar mensagens de log de nossos aplicativos. É muito mais poderoso do que apenas usar instruções de impressão simples. Veja o que ele pode fazer:

  1. Diferentes níveis de log (DEBUG, INFO, WARNING, ERROR, CRITICAL): Esses níveis nos ajudam a categorizar a importância das mensagens. Por exemplo, DEBUG é para informações detalhadas que são úteis durante o desenvolvimento, enquanto CRITICAL é para erros graves que podem interromper o programa.
  2. Formato de saída configurável: Podemos decidir como as mensagens de log serão exibidas, como adicionar timestamps ou outras informações úteis.
  3. As mensagens podem ser direcionadas para diferentes saídas (console, arquivos, etc.): Podemos optar por mostrar as mensagens de log no console, salvá-las em um arquivo ou até mesmo enviá-las para um servidor remoto.
  4. Filtragem de log com base na severidade: Podemos controlar quais mensagens vemos com base em seu nível de log.

Adicionando Logging a reader.py

Agora, vamos alterar seu código para usar o módulo logging. Abra o arquivo reader.py.

Primeiro, precisamos importar o módulo logging e configurar um logger para este módulo. Adicione o seguinte código no topo do arquivo:

import logging

## Set up a logger for this module
logger = logging.getLogger(__name__)

A instrução import logging traz o módulo logging para que possamos usar suas funções. O logging.getLogger(__name__) cria um logger para este módulo específico. Usar __name__ garante que o logger tenha um nome exclusivo relacionado ao módulo.

Em seguida, modificaremos a função convert_csv() para usar o logging em vez de instruções de impressão. Aqui está o código atualizado:

def convert_csv(rows, converter, header=True):
    """
    Convert a sequence of rows to an output sequence according to a conversion function.
    """
    if header:
        headers = next(rows)
    else:
        headers = []

    result = []
    for row_idx, row in enumerate(rows, start=1):
        try:
            ## Try to convert the row
            result.append(converter(headers, row))
        except Exception as e:
            ## Log a warning message for bad rows
            logger.warning(f"Row {row_idx}: Bad row: {row}")
            ## Log the reason at debug level
            logger.debug(f"Row {row_idx}: Reason: {str(e)}")
            continue

    return result

As principais mudanças aqui são:

  • Substituímos print() por logger.warning() para a mensagem de erro. Dessa forma, a mensagem é registrada com o nível de aviso apropriado, e podemos controlar sua visibilidade posteriormente.
  • Adicionamos uma nova mensagem logger.debug() com detalhes sobre a exceção. Isso nos dá mais informações sobre o que deu errado, mas só é exibido se o nível de logging estiver definido como DEBUG ou inferior.
  • O str(e) converte a exceção em uma string, para que possamos exibir o motivo do erro na mensagem de log.

Depois de fazer essas alterações, salve o arquivo.

Testando o Logging

Vamos testar seu código com o logging habilitado. Abra o interpretador Python executando o seguinte comando em seu terminal:

python3

Depois de estar no interpretador Python, execute o seguinte código:

import logging
import reader

## Configure logging level to see all messages
logging.basicConfig(level=logging.DEBUG)

port = reader.read_csv_as_dicts('missing.csv', types=[str, int, float])
print(f"Number of valid rows processed: {len(port)}")

Aqui, primeiro importamos o módulo logging e nosso módulo reader. Em seguida, definimos o nível de logging como DEBUG usando logging.basicConfig(level=logging.DEBUG). Isso significa que veremos todas as mensagens de log, incluindo DEBUG, INFO, WARNING, ERROR e CRITICAL. Em seguida, chamamos a função read_csv_as_dicts do módulo reader e imprimimos o número de linhas válidas processadas.

Você deve ver uma saída como esta:

WARNING:reader:Row 4: Bad row: ['C', '', '53.08']
DEBUG:reader:Row 4: Reason: invalid literal for int() with base 10: ''
WARNING:reader:Row 7: Bad row: ['DIS', '50', 'N/A']
DEBUG:reader:Row 7: Reason: could not convert string to float: 'N/A'
...
Number of valid rows processed: 20

Observe que o módulo logging adiciona um prefixo a cada mensagem, mostrando o nível de log (WARNING/DEBUG) e o nome do módulo.

Agora, vamos ver o que acontece se mudarmos o nível de log para mostrar apenas avisos. Execute o seguinte código no interpretador Python:

## Reset the logging configuration
import logging
logging.basicConfig(level=logging.WARNING)

port = reader.read_csv_as_dicts('missing.csv', types=[str, int, float])

Desta vez, definimos o nível de logging como WARNING usando logging.basicConfig(level=logging.WARNING). Agora você verá apenas as mensagens WARNING, e as mensagens DEBUG serão ocultas:

WARNING:reader:Row 4: Bad row: ['C', '', '53.08']
WARNING:reader:Row 7: Bad row: ['DIS', '50', 'N/A']
...

Isso mostra a vantagem de usar diferentes níveis de logging. Podemos controlar quantos detalhes são mostrados nos logs sem alterar nosso código.

Para sair do interpretador Python, execute o seguinte comando:

exit()

Parabéns! Você agora implementou o tratamento de exceções e o logging adequados em seu programa Python. Isso torna seu código mais confiável e fornece melhores informações quando ocorrem erros.

Resumo

Neste laboratório, você aprendeu vários conceitos-chave sobre tratamento de exceções e logging em Python. Primeiro, você entendeu como as exceções ocorrem durante o processamento de dados e implementou blocos try-except para lidar com elas de forma elegante. Você também modificou o código para continuar processando dados válidos quando ocorrem erros.

Em segundo lugar, você aprendeu sobre o módulo logging do Python e seus benefícios em relação às instruções de impressão. Você implementou diferentes níveis de log, como WARNING e DEBUG, e viu como configurar o logging para diferentes níveis de detalhe. Essas habilidades são cruciais para escrever aplicativos Python robustos, especialmente ao lidar com dados externos propensos a erros, construir aplicativos autônomos ou desenvolver sistemas que precisam de informações de diagnóstico. Agora você pode aplicar essas técnicas aos seus projetos Python para melhor confiabilidade e capacidade de manutenção.