Este arquivo apresenta informações básicas para ajudar você a tratar erros e exceções no seu programa, essencialmente como depurar o seu programa. Este texto é uma referência direta ao cápitulo 11 do livro *Automate The Boring Stuff*(sem tradução para o português) que pode ser acessado nesse [link](https://automatetheboringstuff.com/2e/chapter11/), contendo o conteúdo completo e com mais exemplos.
# Tratamento de erros
Agora que estamos escrevendo programas mais complicados, é muito comum nos depararmos com erros ou bugs, quando isso acontece o compilador irá emitir uma mensagem de erro, porém as vezes o erro é um erro de lógica e dificilmente será identificado pelo compilador da forma correta. Como por exemplo, por que essa variável esta tendo esse valor ou por que o meu programa não executa tal bloco.
Em cassos como esse, é necessário depurar o programa.
Cuidado com o que você pede, o seu computador só vai fazer o que você manda-lo fazer, não vai ler a sua mente e fazer o que você **pretendeu** fazer. Mesmo programadoras profissionais criam bugs o tempo todo, não se sinta desencorajada se o seu programa estiver com problemas.
Felizmente existem ferramentas e técnicas para identificar examatamente o que o seu programa esta fazendo e o que está acontecendo de errado.
## Tratar exceções
Até o momento, ao encontrar um erro ou *exception* o seu programa inteiro para. Ao invés disso, que tal se o seu programa detectasse erros, lidasse com eles e então continuasse executando ?
Python ergue uma exceção(raise an ecpection) ou erro, sempre que tentar executar um código inválido, erros de sintaxe por exemplo. Erros esperados podem ser manipulados com *try* and *except* statements. O código que possivelmente contém um erro entra no bloco try. O programa executa o bloco *except* caso aconteça o erro.
```
try:
<faça algo>
except Exception:
<lide com o erro>
```
O código a seguir lida com o erro ZeroDivisionError:
```
def divisao(divisor):
try:
print(f'Resultado: {42/divisor}')
except ZeroDivisionError:
print('Erro: não pode ser dividido por 0')
print(divisao(2))
print(divisao(12))
print(divisao(0))
print(divisao(1))
```
Após passar pelo erro, o **programa** continua sua execução normalmente.
Outro exemplo é o código a seguir que tenta abrir um arquivo:
```
try:
open('arquivo_inexistente.txt')
except:
print('Algo deu errado')
```
Ao abrir um arquivo, pode acontecer de não achar o arquivo ou que o nome é um diretório e posso especificamente lidar com esse erro ao passar para o except:
```
try:
open('eh_diretorio.txt')
except IsADirectoryError:
print('Esse nome é um dirétorio, não é possível abrir')
```
Note que erros **dentro** da função serão apanhados:
```
def divisao(divisor):
print(f'Resultado: {42/divisor}')
try:
print(divisao(2))
print(divisao(12))
print(divisao(0))
print(divisao(1))
except ZeroDivisionError:
print('Erro: não pode ser dividido por 0')
```
A razão pela qual o programa para é porque quando o bloco *except* é excecutado, o programa não retorna novamente ao bloco *try*, ao invés disso continua indo depois da função executada.
Se antes, apenas um erro faria seu programa quebrar, dessa forma você pode executar o seu código mesmo quando um erro for detectado.
Agora, saiba que é possível erguer erros para o seu próprio código. Erguer uma exceção é um jeito de dizer 'Pare de excecutar o código dessa função e vá para o bloco *except*'
* *raise* keyword
* Chamada para a função Exception()
* Uma string contendo uma mensagem de erro útil passada para a função Excpetion
```
>>> raise Exception('This is the error message.')
Traceback (most recent call last):
File "<pyshell#191>", line 1, in <module>
raise Exception('This is the error message.')
Exception: This is the error message.
```
Se não tiver blocos de *try* e *except* cobrindo a declaração *raise* que ocorreu, o programa para e mostra a mensagem de erro do *except*.
Em um exemplo prático, não é a função sozinha que vai lidar com o erro mas sim o código. Então é comum a declaração *raise* dentro das funções e o bloco *try* e *except* no corpo do código chamando a função. Como o código a seguir que imprime uma caixa de símbolos:
```
def boxPrint(simbolo, largura, altura):
if len(simbolo) != 1:
raise Exception('Símbolo tem de ser apenas um caractere')
if largura <= 2:
raise Exception('Largura deve ser maior do que 2')
if altura <= 2:
raise Exception('Altura deve ser maior do que 2')
print(simbolo * largura)
for i in range(altura - 2):
print(simbolo + (' ' * (largura - 2)) + simbolo)
print(simbolo * largura)
for sym, w, h in (('@', 4, 4), ('O', 20, 5), ('x', 1, 3), ('ZZ', 3, 3)):
try:
boxPrint(sym, w, h)
except Exception as err:
print('An exception happened: ' + str(err))
```
Outro exemplo
O programa usa a declaração *except Exception as err*, uma forma da declaração de *except*. Se um objeto *Exceção*(Exception) é retornado da função, a declaração *except* irá guardar na variável *err*. Então o objeto *Exception* é convertido em uma string ao passar na função *str()* para podermos visualizar a mensagem de erro.
## Traceback
Quando Python encontra um erro, é produzido um tesouro de informações de erro chamadas *traceback*. O traceback inclui a mensagem de erro, o número da linha que causou o erro e a sequência de funções que levaram ao erro. Essa sequência é chamada de *call stack*.
Execute o código:
```
def spam():
bacon()
def bacon():
raise Exception('This is the error message.')
spam()
```
Observe o traceback. Qual é a sequência de chamada das funções ?
É possível obter a mensagem de erro como uma string através da função *traceback.format_exc()*. Essa função é útil se você quer a informação de algum *except* de um tracceback mas também quer que o erro seja tratado no seu programa. É necessário importar o módulo *traceback* no programa.
```
>>> import traceback
>>> try:
... raise Exception('This is the error message.')
except:
... errorFile = open('errorInfo.txt', 'w')
... errorFile.write(traceback.format_exc())
... errorFile.close()
... print('The traceback info was written to errorInfo.txt.')
```
Assim, ao invés do seu programa parar quando ocorrer um erro, o seu programa grava a informação do traceback em um arquivo de texto e deixa o programa continuar executando. Você pode olhar depois para o arquivo de texto, quando você estiver pronta para debugá-lo.
## Assertions
Um *assertion* é uma tipo de checagem que pode ser feita para garantir que o seu código não esta fazendo algo obviamente errado. É feita pela declaração *assert* e se falhar, um *AssertionError* excpetion é erguido. No código, uma declaração *assert* consiste no seguinte:
* *assert* como palavra-chave
* uma condição(uma expressão avaliada para *True* ou *False*)
* uma vírgula
* uma string para mostrar quando a condição for *False*
Execute o código a seguir:
```
>> ages = [26, 57, 92, 54, 22, 15, 17, 80, 47, 73]
>>> ages.sort()
>>> ages
[15, 17, 22, 26, 47, 54, 57, 73, 80, 92]
>>> assert ages[0] <= ages[-1] # Assegure que a primeira idade é menor que a última
```
A declaração *assert* nesse trecho garanteque o primeiro item em *ages* seja menor ou igual ao último item. Se a função *sort()* fez o seu trabalho, então a afirmação será *True*. Já que a expressão foi avaliada para *True* então a declaração *assert* não faz nada.
Agora imagine que cometemos um erro e acidentalmente colocamos a função de lista *reverse()* ao inves de sort(). Quando escrevermos o código a seguir no terminal a declaração *assert* erguirá um *AssertionError*:
Escreva o código a seguir no terminal interativo:
```
>>> ages = [26, 57, 92, 54, 22, 15, 17, 80, 47, 73]
>>> ages.reverse()
>>> ages
[73, 47, 80, 17, 15, 22, 54, 92, 57, 26]
>>> assert ages[0] <= ages[-1] # Assegure que a primeira idade é menor que a última
```
Ao contrário das *exceptions*, o seu código não deverá lidar com as declarações *assert* com *try* e *except*, se uma afirmação(*assertion*) falhar, o seu programa **dever** parar.
*Assertions* são apenas para erros da programadora, não erros cometidos pela usuária. *Assertions* devem apenas falhar enquanto o programa esta em desenvolvimento, a usuária não deve ver um erro de *assertion* no produto final.
Para erros que o seu programa vai encontrar como uma parte normal da sua operação (como um arquivo não ser encontrado ou a usuária digitar uma data inválida), forneça um *exception* ao invés de detectar esse erro com uma declaração *assert*. Você também não deve usar as declarações *assert* no lugar de levantar exceções, pois usuárias podem desligar as afirmações.
## Depurador
Debugger é uma ferramenta que para testar e depurar o seu programa. Está presente em diversos IDEs e em versões gráficas ou pela linha de comando. Com essa ferramenta é possível interagir com seu programa durante a sua execução uma linha por vez ou pausar a execução em um ponto especifíco, examinando-o para entender seu processo e achar erros.
Consiste nas seguintes funções:
* Continue
Executa o programa normalmente até que termine o programa ou encontre um *breakpoint
* Step In
Executa a próxima linha do código e parar. Se a próxima linha for uma função o programa "entra" nessa função e vai para a primeira linha dessa função.
* Step Over
Similar ao Step In, porém se a próxima linha de código for uma função, então essa chamada é "pulada". A função é executada.
* Step Out
Executa as linhas do código e retorna para a atual função. Se você entrou em uma função pelo Step In e agora quer executar o resto do código da função e "sair" da função atual.
* Stop
Para o processo do debugger.
* Breakpoints
Pode ser colocado em uma linha específica do código e força o debugger a parar quando o essa linha for executada.
### Debuggers
Linha de comando:
* pdb
* ipdb
* outros..
Gráficos:
* pudb
* IDEs
Visual Studio Code
PyCharm
* Outros
### Referências
[Try and Except](https://pythonbasics.org/try-except/)
["Goodbye Print Statements, Hello Debugger!"](https://www.youtube.com/watch?v=HHrVBKZLolg)