Exceções em Python – parte 1

excecoes_em_python-1_abertura

O controle de exceções (ou de erros, se preferir) é um recurso presente em diversas linguagens de programação para interceptar a ocorrência de algo inesperado¹ durante a execução do programa e tentar contornar o fato para poder prosseguir com a execução ou mesmo, interrompê-la para evitar maiores danos.

(¹) Uma falha, erro, defeito etc, ou seja, uma exceção visto que não deveria ter ocorrido… 🙂

Criando um programa de testes

Para ajudar a demonstrar, um programa bem simples e que tem o único propósito de contar² até 100.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function
from time import sleep
from sys import stdout

print("Este programa contará até 100.")

contador = 0
while contador < 100:
    print (".", end="")
    stdout.flush() # força a atualização da tela.
    contador += 1
    sleep(.03125) # espera 1/32 segundos.

print (" FIM!")

Que ao ser executado retornará apenas a contagem e mais algumas mensagens…

$ python3 contar_ate_100-a.py
Este programa contará até 100.
.....................................................................
............................... FIM!

Agora que ele foi testado é possível provocar intencionalmente uma exceção (ou erro) nele.

    ...
    stdout.flush() # força a atualização da tela.
    if contador == 25:
        comando_inexistente
    contador += 1
    ...

Assim….

$ python3 contar_ate_100-b.py
Este programa contará até 100.
..........................Traceback (most
recent call last):
 File "./contar_ate_100-b.py", line 14, in <module>
 comando_inexistente
NameError: name 'comando_inexistente' is not defined

E agora é possível prosseguir! 🙂

(²) Na verdade ele imprimirá cem pontos na tela, não espere uma contagem numérica… 😀


Observação : Apesar de ter usado a versão 3.x do Python para executar os programas, estes funcionarão da mesma forma na versão 2.7.

Tratando uma exceção

Em Python o tratamento das exceções é feita através dos comandos try, que define um bloco de comandos onde eventuais exceções serão interceptados, e except, que conterá a rotina de tratamento destas — ou seja, pode servir para notificar o usuário, salvar um arquivo de log, tentar fazer a mesma coisa com outra abordagem, abortar de vez a execução, ignorar e seguir adiante etc.

    ...
    stdout.flush() # força a atualização da tela.
    try:
        if contador == 25:
            comando_inexistente
    except:
        print("<<\nexceção interceptada\n>>", end="")
    contador += 1
    ...

Neste caso a interceptação das exceções ocorrerá somente dentro do bloco do if, direcionando a execução diretamente para o except que simplesmente informará que o erro foi interceptado e prosseguirá normalmente com a execução…

$ python3 contar_ate_100-c.py 
Este programa contará até 100.
..........................<<
exceção interceptada
>>...................................................................
....... FIM!

Aliás, o bloco tryexcept poderia ser colocado em um escopo mais amplo do programa como, por exemplo, no bloco while.

...
contador = 0
try:
    while contador < 100:
    ...
except:
    print("<<\nexceção interceptada\n>>", end="")
...

A diferença é que neste caso não é possível prosseguir com a execução do laço e o programa encerra logo em seguida.

$ python3 contar_ate_100-b_while.py 
Este programa contará até 100.
..........................<<
exceção interceptada
>> FIM!

Portanto, tenha em mente que a posição do bloco try-catch é importante, principalmente considerando o que a rotina de interceptação da exceção precisará realizar.

Tratando mais de uma exceção

No exemplo anterior foi feito um tratamento genérico de exceções, ou seja, independentemente do que tenha ocorrido dentro do try a execução é desviada para o except. Mas o que fazer quando se deseja distinguir uma exceção da outra? Por exemplo, como distinguir a falha de leitura/gravação de arquivo de um de simples erro de digitação?

        ...
        if contador == 25:
            comando_inexistente
        elif contador == 50:
            limite = int("q23")
        ...

Agora um “descuido” pois variável ‘limite’ receberá o valor resultante da conversão de uma string que contém um caractere e que resultará em uma exceção…

$ python3
Python 3.6.3 (default, Oct 3 2017, 21:45:48) 
[GCC 7.2.0] on linux
>>> int("q23")
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'q23'

Ao executar o programa…

$ python3 contar_ate_100-d.py 
Este programa contará até 100.
..........................<<
exceção interceptada
>>.........................<<
exceção interceptada
>>................................................. FIM!

Ambas as exceções foram interceptadas mas qual delas foi a intencionalmente criada (a chamada ao “comando_inexistente”) e qual foi um simples descuido (a simulação do erro de digitação)?

Não sendo definida a exceção a tratar o Python cuidará todas elas, até mesmo a pressão das teclas «Ctrl»+«C» será capturada — o que pode ser uma surpresa bem desagradável.

    ...
    try:
        if contador == 25:
            comando_inexistente
        elif contador == 50:
            limite = int("q23")
    except NameError:
        print("<<\nexceção interceptada\n>>", end="")
    ...

Desta forma basta definir no comando except qual das exceções embutidas no Python deverá ser tratada.

$ python3 contar_ate_100-e.py 
Este programa contará até 100.
..........................<<
exceção interceptada
>>.........................Traceback (most recent call last):
 File "./contar_ate_100-e.py", line 17, in <module>
 limite = int("abc")
ValueError: invalid literal for int() with base 10: 'q23'

Neste caso a exceção tratada é a NameError que ocorre justamente quando é utilizado um nome não definido para variável, função, classe etc — o caso de “comando_inexistente” — enquanto que a tentativa de converter a string “q23” em um número inteiro produz outra exceção, a ValueError, que acaba sendo tratada diretamente pelo interpretador e assim a execução acaba interrompida.

Precisando tratar mais de uma exceção, basta criar outros blocos de except, por exemplo:

    ...
    try:
        ...
    except NameError:
        print("<<\nexceção interceptada\n>>", end="")
    except ValueError:
        print("<<\noutra exceção aqui!\n>>", end="")
    ...

Assim duas, ou mais, exceções podem ser tratadas de forma diferente…

$ python3 contar_ate_100-f.py 
Este programa contará até 100.
..........................<<
exceção interceptada
>>.........................<<
outra exceção aqui!
>>................................................. FIM!

E havendo a necessidade é possível finalizar o bloco com um tratamento genérico para a exceção.

Fim desta parte

Por enquanto é isto, os programas usados nos exemplos acima estão no repositório Git dos arquivos do blog, na próxima parte será a vez de recuperar detalhes sobre a exceção que ocorreu (útil para depuração) e também aprimorar a forma como o programa as trata — abortar a execução ou tentar fazer algo para corrigir/minimizar o ocorrido — e, claro, customizar o tratamento de exceções criando  suas próprias.

Até!

Anúncios

Um comentário sobre “Exceções em Python – parte 1

  1. Pingback: Exceções em Python – parte 2 | giovannireisnunes

Os comentários estão desativados.