Deu erro! E agora, o que eu faço?

Traceback (most recent call last):
  File "novo.py", line 12, in <module>
    hello()
  File "novo.py", line 10, in hello
    monta_lista(10)
  File "novo.py", line 5, in monta_lista
    return x[max]
IndexError: list index out of range

Se você sente calafrios só de ver a mensagem de erro acima, este post é para você. Você já ficou p*** da vida com o interpretador porque aquele programa recém escrito, perfeitinho, começou a apresentar mensagens de erro indecifráveis? Vá com calma, na maioria esmagadora dos casos, a culpa não é do interpretador, da linguagem, do sistema operacional ou do hardware. A culpa é quase sempre sua! (lembre-se sempre disso). Frases como “esse compilador está errado!” são muito comuns nos momentos de frustração, mesmo para programadores experientes. Mas caia na real, o compilador/interpretador só está fazendo o trabalho dele, o errado da história, em geral, é você*.

No momento de desespero, quando você não está entendendo o que está acontecendo para que o seu programa tenha problemas, é preciso que você conheça os mecanismos adequados para simplificar a tarefa de descobrir os problemas em seu código. Neste post nós vamos ver como podemos proceder para facilitar a descoberta de erros no nosso código. Vamos começar pelo basicão: como encontrar e identificar o erro em nosso código.

*É claro que não é impossível que você realmente tenha encontrado algum defeito no compilador ou no sistema operacional, mas esgote todas as possibilidades antes de partir para tal hipótese.

Entendendo melhor as mensagens de erro do Python

O traceback é a informação que o interpretador Python fornece para nos ajudar a identificar o problema em nosso código. Ele contém algumas informações úteis:

  • a sequência de chamadas de funções até o ponto onde ocorreu o problema;
  • o número da linha onde o erro foi gerado;
  • o tipo de erro que ocorreu, bem como uma pequena mensagem informativa sobre o ocorrido.

Às vezes o traceback parece meio enigmático, mas você verá que é bem fácil de entendê-lo. Vejamos um exemplo de um traceback bem simples:

Traceback (most recent call last):
  File "erro1.py", line 4, in <module>
    print (z+y)/x
ZeroDivisionError: integer division or modulo by zero

Mesmo sem olhar o código que o gerou, já podemos extrair algumas informações úteis:

  1. o erro foi gerado na linha 4 do arquivo erro1.py;
  2. o erro que ocorreu é chamado de ZeroDivisionError, indicando que ocorreu uma tentativa de dividir um valor por zero;
  3. o código da linha que gerou o problema é print (z+y)/x

Olhando agora para o código-fonte do programa que gerou o erro acima, podemos identificar logo o erro:

1  x = 0
2  y = 10
3  z = 3
4  print (z+y)/x

Tá na cara, estamos dividindo um valor por x, sendo que x é zero. Divisão por zero é uma indefinição matemática e nem o computador sabe lidar com isso.

Outro exemplo

Agora você está escrevendo um programinha que vai manipular uma lista com valores aleatórios.

#! -*- encoding:utf-8 -*-
from random import shuffle
TAM = 5
lista = range(TAM)
shuffle(lista)
print 'Primeiro:', lista[0]
print 'Último:', lista[TAM]

Ao executar, se depara com o seguinte saída, incluindo um traceback:

Primeiro: 3
Último:
Traceback (most recent call last):
  File "/home/user/src/erro2.py", line 7, in <module>
    print 'Último:', lista[TAM]
IndexError: list index out of range

Perceba que as duas primeiras linhas acima fazem parte da saída gerada pelo seu programinha. Vamos agora encontrar e identificar o erro:

  • De acordo com o traceback, o erro foi gerado na linha 7 do nosso arquivo. Essa é a última linha (print 'Último:', lista[TAM]).
  • Outra informação muito importante na saída apresentada acima é o tipo de erro que ocorreu: IndexError: list index out of range (adaptado em pt-br: Erro de índice: índice fora da faixa da lista). O IndexError significa que estamos tentando acessar alguma posição da lista que vai além do tamanho dela. Por exemplo, em uma lista de dez elementos, você não pode acessar o décimo-primeiro elemento, pois ele não existe. Se você tentar, vai receber um IndexError na cabeça.

Agora você já tem alguma informação sobre o que pode estar acontecendo: é na última linha e é algum problema relacionado a acesso a posições inexistentes da lista.

Vamos, antes de mais nada lembrar rapidinho como se dá a indexação de listas (e outras sequências) em Python. Veja a lista abaixo, e seus respectivos índices:

lis
+-----+-----+-----+-----+-----+
|  2  |  9  |  6  |  8  |  7  |
+-----+-----+-----+-----+-----+
   0     1     2     3     4

Você já sabe que o índice para acesso ao primeiro elemento da lista lis é 0. Para imprimir o primeiro elemento, você faria print lis[0]. Sabendo que o primeiro índice é 0, fica óbvio que o último não é o tamanho da lista, mas sim o tamanho da lista – 1. Ou seja, em uma lista de 5 elementos (como lis), para imprimir o último elemento, você deve utilizar print lis[4] ao invés de print lis[5]. Agora reveja o traceback e o código-fonte do programa e descubra o erro. 😛

Mais um exemplo

Agora você vai fazer um programa para montar e imprimir URLs de acordo com os parâmetros de protocol, host e path. Veja abaixo o exemplo:

def URL(protocol, host, path):
    return protocol + "://" + host + "/" + path 

def print_urls():
    print URL('http', 'www.example.com', 'path/to/file.html')
    print URL('ftp', 'www.example.com', 'path/to/file.html')
    print URL('http', 'www.example.com', 2)

print_urls()

Porém, pra variar, ao executar, lá vem o maldito traceback (apesar de odiá-lo, acredite, seria muito pior sem ele).

http://www.example.com/path/to/file.html
ftp://www.example.com/path/to/file.html
Traceback (most recent call last):
File "/home/user/src/erro3.py", line 11, in <module>
  print_urls()
File "/home/user/src/erro3.py", line 8, in print_urls
  print URL('http', 'www.example.com', 2)
File "/home/user/src/erro3.py", line 2, in URL
  return protocol + "://" + host + "/" + path 
TypeError: cannot concatenate 'str' and 'int' objects

E agora, o que diabos está acontecendo? Primeiramente, você deve tentar entender a saída do programa. Veja que as duas primeiras linhas indicam que as duas primeiras tentativas de impressão de URLs ocorreram com sucesso. Então, mesmo sem olhar o traceback, você já imagina que algo ocorreu de errado na terceira URL (que deveria mostrar http://www.example.com/2). Analise o traceback, seguindo o procedimento que vimos anteriormente (encontrar onde pode estar o erro e então identificá-lo, para depois corrigí-lo).

A antepenúltima linha do traceback diz que o erro está na linha 2, na função URL (File "/home/user/src/erro3.py", line 2, in URL). A linha 2 é return protocol + "://" + host + "/" + path. Mas peraí, se você olhar o traceback de cima para baixo, verá que ele aponta várias linhas como fontes do erro: 11, 8 e 2. Observe que ele mostra o rastro (trace) de chamadas de funções que levaram ao código com defeito. Como o próprio traceback nos mostra, ocorreu a seguinte chamada de funções que levou ao erro apresentado acima:

print_urls()  -->  print URL('http', 'www.example.com', 2)  -->  return protocol + "://" + host + "/" + path 
    11                                 8                                                2

Talvez você não esteja conseguindo descobrir o erro. Então, vamos analisar mais uma informação do traceback: o tipo do erro. De acordo com o que foi apresentado na última linha do erro, temos um erro do tipo TypeError (erro de tipagem), com a mensagem de erro: cannot concatenate 'str' and 'int' objects (não é possível concatenar objetos dos tipos ‘str’ e ‘int’). Ou seja, estamos em algum lugar passando um número inteiro para ser concatenado às strings que são utilizadas para compor a URL. Agora ficou moleza, pois na linha 8 estamos passando o argumento 2 (um valor do tipo int) para o parâmetro path da função URL() (que é usado nessa função para ser concatenado a valores do tipo str). Para corrigir é fácil, basta substituir o 2 do tipo inteiro pelo '2' do tipo string. A linha 8 ficaria assim:

print URL('http', 'www.example.com', '2')

Mas às vezes os erros não estão tão explícitos assim, e somente analisar o traceback não é o suficiente para descobrirmos o problema de nosso código. Então teremos que partir para outras abordagens, como veremos a seguir.

(Se você quiser conhecer os principais erros e exceções que podem ocorrer em Python, consulte a documentação oficial.)

Muito além do traceback

Muitas vezes é preciso que analisemos a execução do programa em questão. Essa análise envolve descobrirmos os valores que as variáveis estão assumindo e quais caminhos no código o programa está percorrendo durante sua execução. A forma mais comum que existe para fazermos isso e que para muitos é a solução definitiva para o problema, é encher o código de prints, mostrando assim no console os valores das variáveis, para que possamos ver se o programa está se comportando da forma desejada. Existem outras formas também, usando ferramentas específicas, que permitem que o programa seja pausado, executado linha por linha e inspecionado. Tanto o uso dos prints quanto o uso de ferramentas específicas é chamado de depuração de código (debugging, eliminação de bugs).

O print

Quem nunca encheu o código de prints para tentar entender o que está se passando no programa atire a primeira pedra. Acaba que essa é a solução mais natural para o cara que está iniciando. A regra é simples: imprima o valor das variáveis que lhe interessam, principalmente em pontos como entradas de expressões condicionais (if-elif-else); antes, durante e depois da execução de um laço de repetição (for, while); antes e depois da chamada de uma função que modifica valores de nossas variáveis; e em toda situação que ficamos na dúvida sobre o que está acontecendo com nossas variáveis.

Veja um exemplo do uso de prints para depurar um programa:

x = 0
x = faca_algo_com(x)
print 'pre-while: %s' % (x, )
while x > 0:
    x = faca_algo_com(x)
    if x % 2 == 0:
        x += 1
    print 'while: %s' % (x, )
print 'pos-while: %s' % (x, )

No exemplo acima, usamos 3 prints para termos melhor entendimento do que está acontecendo durante a execução do programa. Em programas que já lançam muitos valores na saída padrão, pode ser útil lançar as mensagens de depuração na saída padrão de erros do shell, para que possamos, se preciso for, analisá-las em separado do resto do programa:

import sys
x = 0
x = faca_algo_com(x)
sys.stderr.write('pre-while: %s\n' % (x, ))
while x > 0:
    x = faca_algo_com(x)
    if x % 2 == 0:
        x += 1
    sys.stderr.write('while: %s\n' % (x, ))
sys.stderr.write('pos-while: %s\n' % (x, ))

Mas, tenha cuidado, pois às vezes o excesso de informações vai atrapalhar mais do que ajudar.

Os loggers

Uma alternativa interessante aos prints é o uso de loggers. O log de um programa é, em geral, um objeto no qual registramos informações, para uma possível análise posterior. Em geral, utilizamos o log para registro da ocorrência de eventos durante a execução de um programa. A ideia principal é basicamente a mesma do que usar os prints, mas na prática existem várias vantagens no uso de um logger.

O recurso mais interessante dos loggers é a possibilidade de “categorizar” as informações que vamos jogando na tela, de acordo com o seu grau de importância. Por exemplo, o mecanismo de logging padrão do Python possui 5 níveis: DEBUG, INFO, WARNING (nível padrão), ERROR e CRITICAL. Veja o exemplo:


import logging

logging.debug('depurando ...')
logging.info('informando ...')
logging.warning('alertando ...')
logging.error('assustando ...')
logging.critical('apavorando ...')

Se o programa acima for executado, a saída será:

WARNING:root:alertando ...
ERROR:root:assustando ...
CRITICAL:root:apavorando ...

Perceba que as duas primeiras linhas do logging não foram refletidas na saída do programa. Isso porque o nível padrão do logger é o warning, o que significa que somente mensagens de warning e de categorias mais importantes (no caso, error e critical) serão impressas.

Usando o logger, podemos incluir várias mensagens de depuração no código, e ativar seu aparecimento na saída do programa somente quando tivermos interesse.

Veja mais em: http://docs.python.org/2/library/logging

O debugger

Depuradores são ferramentas feitas para auxiliar o desenvolvedor a descobrir falhas em seu código. Eles permitem que executemos um programa passo-a-passo, que a cada passo inspecionemos as variáveis, que verifiquemos em que seção do código está a execução do programa, além de outros recursos. Para mim, o melhor dos recursos é poder inspecionar os valores aos quais as variáveis referenciam durante a execução passo-a-passo do programa.

O depurador que vamos ver em seguida é o pdb, que é o depurador padrão distribuído com Python. Basta que executemos o depurador, informando a ele qual o programa que queremos depurar. Por exemplo, para depurar um programa chamado prog.py, invocamos o pdb
da seguinte forma (em um shell do sistema operacional):

$ python -m pdb prog.py

Teste você mesmo, baixando o arquivo prog.py. Veja na imagem a seguir um exemplo de execução de um programa usando o pdb:

pdb

Ao iniciar a execução, o depurador para e aguarda por comandos do usuário, que podem ser: execute a atual e pule para a próxima linha, entre dentro da função, mostre o valor de uma variável/expressão, mostre o código fonte, etc.

Alguns comandos úteis do pdb:

  • list: mostra um trecho do código em execução, com destaque para a linha atual;
  • next: comando que indica ao pdb que ele deve seguir para a próxima linha de execução, sem adentrar em funções quando houverem chamadas;
  • step: indica ao pdb para que ele siga em frente, entrando na função se a linha atual for uma chamada de função;
  • pp <nome_de_variável>: mostra na tela o valor referenciado pela variável nome_de_variável.

A seguir, vou apresentar um processo de depuração, para que você entenda melhor do que se trata e como podemos fazê-lo. Vamos testar um programa que faz a multiplicação de dois valores através de sucessivas somas:

def multiplica(x, y):
  result = 0
  while y > 0:
      result = result + x
      y = y - 1
  return result

z = 10
k = 2
r = multiplica(z, k)
print r

Veja o vídeo abaixo, onde demonstro uma pequena sessão de depuração do programa acima:

Como você pode perceber, o mais interessante do uso do depurador é a análise detalhada que é possível que façamos enquanto o programa está em execução. No vídeo, inspecionamos os valores de variáveis (com o comando pp), verificamos o código em execução do programa (o list mostra a linha em execução atualmente, bem como algumas linhas acima e abaixo), passamos de linha em linha (usando o comando next) e até mesmo entramos em funções, para verificar o que é feito dentro delas (com o comando step). Normalmente, é disso que se trata uma sessão de depuração, uma análise do programa em execução, inspeção de valores, verificação de caminhos percorridos pela execução no código do programa. Assim, o programador pode obter boas pistas sobre o problema que está enfrentando.

A seguir, uma tabela contendo os principais comandos do pdb e suas funcionalidades:

Comando Atalho Uso
help h Mostra a ajuda do comando.
break x b Insere um breakpoint na linha x, um ponto de parada, até o qual o programa será executado sem interatividade. A partir dali, a execução fica sob o controle do usuário do depurador.
step s Prossegue a execução até a próxima linha de código, inclusive entrando em funções.
next n Parecido com step, mas executa chamadas a funções como comandos comuns, sem entrar nelas.
pp exp pp Imprime o valor da expressão exp, podendo exp ser uma simples variável ou uma expressão complexa.
quit q Fecha o depurador

Mais comandos em: http://docs.python.org/2/library/pdb.html#debugger-commands

Veja mais sobre o pdb em (em inglês):

Breakpoints

Para que você não precise excutar o programa todo, linha por linha, next por next, desde o começo até o ponto que lhe interesse, você pode usar um breakpoint. Um breakpoint é um ponto de parada, de forma que o programa é executado normalmente (sem interatividade) até o momento em que encontrar o breakpoint. A partir dali, o depurador passa a aguardar pelos comandos do usuário. Breakpoints são muito úteis, pois em geral estamos interessados na depuração de somente um trecho do código, onde desconfiamos que esteja ocorrendo o erro.

winpdb

Se você prefere um ambiente gráfico, vários IDEs fornecem a integração com depurador na sua interface. Basta escolher um IDE e usar a depuração sempre que for preciso.

Mas, se você não quiser usar um IDE só por isso, você pode utilizar o winpdb, que é um depurador gráfico para Python. Em uma janela só, você vê o código, uma marcação indicando a linha em execução, uma tabela contendo todas as informações que estão no escopo local e global, além de suportar avaliação de expressões, e até mesmo, mudanças nos valores das variáveis do programa em tempo de execução.

No vídeo a seguir eu mostro um exemplo de execução de um programa usando o winpdb:

Enfim…

Se você sofre muito para encontrar os problemas existentes no seu código, aprenda a usar um depurador, seja ele gráfico ou em texto, e se acostume a usá-lo. Usar um depurador acaba sendo muito mais eficiente do que encher o código com prints, pois ele permite que paremos a execução em um determinado ponto, que alteremos valores, e que analisemos com mais calma a execução do programa, tendo sempre uma visão melhor do estado atual deste.

Atenção: os códigos apresentados neste post foram elaborados com o único propósito de mostrar uma situação em que erros podem ocorrer. Assim, de forma alguma eles podem ser considerados exemplos de boas práticas.