5. As exceções
Vamos agora debruçar-nos sobre as exceções.

5.1. script [exceptions_01]
O primeiro script ilustra a necessidade de gerir as exceções.
Provocamos deliberadamente um erro para ver o que acontece (exceptions-01.py):
A linha 4 indica-nos:
- o tipo da exceção: ZeroDivisionError;
- a mensagem de erro associada: «division by zero». Está em inglês. É algo que talvez queiramos alterar.
Uma regra essencial é que devemos fazer tudo o que for possível para evitar as exceções geradas pelo interpretador Python. Temos de gerir os erros nós próprios.
A sintaxe para a gestão de exceções é a seguinte:
try:
actions susceptibles de lancer une exception
except (Ex1, Ex2…) as ex:
actions de gestion de l'exception [ex]
except (Ex11, Ex12…) as ex:
actions de gestion de l'exception [ex]
finally:
actions toujours exécutées qu'il y ait exception ou non
No bloco «try», a execução das ações é interrompida assim que ocorre uma exceção (erro). Nesse caso, a execução prossegue com as ações de uma das cláusulas «except»:
- linha 3: se a exceção [ex] que ocorreu for de um tipo pertencente à tupla (Ex1, Ex2…) ou derivado de um deles, então as ações da linha 4 são executadas;
- linha 5: se a exceção não tiver sido interceptada pela linha 3 e existir outra cláusula [except], então ocorre o mesmo processo. E assim por diante…;
- pode haver tantas cláusulas [except] quantas forem necessárias para gerir os diferentes tipos de exceção que possam ocorrer no [try];
- se a exceção não tiver sido tratada por nenhuma das cláusulas [except], então será reenviada para o código chamador. Se este se encontrar, por sua vez, numa estrutura try/except, a exceção é novamente tratada; caso contrário, continua a subir pela cadeia de métodos chamados. Em última instância, chega ao interpretador Python. Este interrompe então o programa em execução e exibe uma mensagem de erro do tipo apresentado no exemplo anterior. A regra é, portanto, que o programa principal deve interceptar todas as exceções que possam ser propagadas a partir dos métodos chamados;
- linha 7: a cláusula [finally] é sempre executada, quer tenha ocorrido uma exceção (continuação do «except») quer não (continuação do «try»). Isto é válido mesmo que tenha ocorrido uma exceção e esta não tenha sido interceptada. Neste caso, a cláusula [finally] será executada antes de a exceção ser propagada para o código chamador;
Uma exceção transporta consigo informações sobre o erro que ocorreu. Estas podem ser obtidas com a seguinte sintaxe:
except MyException as exception:
[exception] é a exceção que ocorreu. [exception.args] representa o tuplo dos parâmetros da exceção.
Para lançar uma exceção, utiliza-se a sintaxe
raise MyException(param1, param2…)
onde, na maioria das vezes, MyException é uma classe derivada da classe BaseException. Os parâmetros passados ao construtor da classe estarão disponíveis na cláusula «except» das estruturas de interceção de exceções com a sintaxe [ex.args], se [ex] for a exceção interceptada pela cláusula [except].
Estes conceitos são ilustrados pelo seguinte script.
5.2. script [exceptions_02]
O seguinte script trata explicitamente os erros:
# gestão de exceções
essai = 0
# provoca-se um erro e este é tratado
x = 2
try:
x = 4 / 0
except ZeroDivisionError as erreur:
# o erro é a exceção interceptada
print(f"essai n° {essai} : {erreur}")
# o valor de x não se alterou
print(f"x={x}")
# recomeça-se
essai += 1
try:
x = 4 / 0
except BaseException as erreur:
# intercepta-se a exceção mais geral
# o erro é a exceção interceptada
print(f"essai n° {essai} : {erreur}")
# é possível interceptar diferentes tipos de exceções
# a execução pára no primeiro [except] capaz de tratar a exceção
essai += 1
try:
x = 4 / 0
except ValueError as erreur:
# esta exceção não ocorre aqui
print(f"essai n° {essai} : {erreur}")
except BaseException as erreur:
# intercepta-se a exceção mais geral
print(f"essai n° {essai} : (Exception) {erreur}")
except ZeroDivisionError as erreur:
# intercepta-se um tipo específico
print(f"essai n° {essai} : (ZeroDivisionError) {erreur}")
# repetimos o processo alterando a ordem
essai += 1
try:
x = 4 / 0
except ValueError as erreur:
# esta exceção não ocorre aqui
print(f"essai n° {essai} : {erreur}")
except ZeroDivisionError as erreur:
# intercepta-se um tipo específico
print(f"essai n° {essai} : (ZeroDivisionError) {erreur}")
except BaseException as erreur:
# intercepta-se a exceção mais geral
print(f"essai n° {essai} : (Exception) {erreur}")
# uma cláusula «except» sem argumentos
essai += 1
try:
x = 4 / 0
except:
# não nos interessa a natureza da exceção
print(f"essai n° {essai} : il y a eu un problème")
# outro tipo de exceção
essai += 1
try:
# x não pode ser convertido num número inteiro
x = int("x")
except ValueError as erreur:
# o erro é a exceção interceptada
print(f"essai n° {essai} : {erreur}")
# uma exceção transporta informações numa tupla acessível ao programa
essai += 1
try:
x = int("x")
except ValueError as erreur:
# o erro é a exceção interceptada
print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")
# é possível lançar exceções
essai += 1
try:
raise ValueError("param1", "param2", "param3")
except ValueError as erreur:
# o erro é a exceção interceptada
print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")
# é possível criar as próprias exceções
# devem derivar da classe [BaseException]
class MyError(BaseException):
pass
# lança-se a exceção MyError
essai += 1
try:
raise MyError("info1", "info2", "info3")
except MyError as erreur:
# o erro é a exceção interceptada
print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")
# é lançada a exceção MyError com uma mensagem de erro
essai += 1
try:
raise MyError("mon msg d'erreur")
except MyError as erreur:
# o erro é a exceção interceptada
print(f"essai n° {essai} : {erreur.args[0]}")
# a cláusula finally é sempre executada
# quer haja ou não uma exceção
essai += 1
x = None
try:
x = 1
except:
# exceção
print(f"essai n° {essai} : exception")
finally:
# executada em todos os casos
print(f"essai n° {essai} : finally x={x}")
essai += 1
x = None
try:
x = 2 / 0
except:
# exceção
print(f"essai n° {essai} : exception")
finally:
# executado em todos os casos
print(f"essai n° {essai} : finally x={x}")
# não é obrigatório incluir uma cláusula [except]
essai += 1
try:
# provoca um erro
x = 4 / 0
finally:
# executado em todos os casos
print(f"essai n° {essai} : finally x={x}")
Notas:
- linhas 4-12: trata-se de uma divisão por zero;
- linha 8: intercepta-se a exceção exata que ocorre e exibe-se;
- linha 12: devido à exceção que ocorreu, x não recebeu qualquer valor na linha 7 e, por isso, o seu valor não se alterou;
- linhas 14-21: repetimos o mesmo procedimento, mas interceptando uma exceção de nível superior do tipo BaseException. Como a exceção ZeroDivisionError deriva da classe BaseException, a cláusula except irá interrompê-la;
- linhas 23-36: inserem-se várias cláusulas except para gerir vários tipos de exceção. Será executada apenas uma cláusula except ou nenhuma, caso a exceção não corresponda a nenhuma cláusula «except»;
- linhas 38-50: repete-se o mesmo procedimento, alterando a ordem das cláusulas [except] para demonstrar a função destas;
- linhas 52-58: a cláusula except pode não ter nenhum argumento. Nesse caso, interrompe todas as exceções;
- linhas 60-67: introduzem a exceção ValueError;
- linhas 69-75: recuperam-se as informações transportadas pela exceção;
- linhas 77-83: apresentam a forma de lançar (raise) uma exceção;
- linhas 86-106: ilustram a utilização de uma classe de exceção proprietária MyError. A classe MyError limita-se a derivar da classe base BaseException. Não acrescenta nada à sua classe base. Mas agora pode ser nomeada explicitamente nas cláusulas «except»;
- linhas 108-130: ilustram a utilização da cláusula finally;
- linhas 132-139: estas linhas mostram que a cláusula [except] não é obrigatória;
Os resultados no ecrã são os seguintes:
5.3. script [exceptions_03]
Este novo script ilustra a propagação das exceções na cadeia de funções chamadoras:
# uma exceção proprietária
class MyError(BaseException):
pass
# três funções
def f1(x: int) -> int:
# não se tratam as exceções — estas são propagadas automaticamente
return f2(x)
def f2(y: int) -> int:
# não se tratam as exceções — estas são propagadas automaticamente
return f3(y)
def f3(z: int) -> int:
if (z % 2) == 0:
# se z for par, lança-se uma exceção
raise MyError("exception dans f3")
else:
return 2 * z
# ---------- main
# as exceções são propagadas pela cadeia de métodos chamados
# até que um método as intercepte. Neste caso, será o método «main»
try:
print(f1(4))
except MyError as erreur:
print(f"type : {type(erreur)}, arguments : {erreur.args}")
# três outras funções que enriquecem as exceções que ela reenvia
def f4(x: int) -> int:
try:
return f5(x)
except MyError as erreur:
# a exceção é enriquecida e, em seguida, relançada
raise MyError("exception dans f4", erreur)
def f5(y: int) -> int:
try:
return f6(y)
except MyError as erreur:
# enriquecemos a exceção e, em seguida, relançamo-la
raise MyError("exception dans f5", erreur)
def f6(z: int) -> int:
if (z % 2) == 0:
# lança-se uma exceção se z for par
raise MyError("exception dans f6")
else:
return 2 * z
# ---------- main
try:
print(f4(4))
except MyError as erreur:
# exibição da exceção
print(f"type : {type(erreur)}, arguments : {erreur.args}")
# é possível percorrer a pilha de exceções
err = erreur
# exibe-se a mensagem de erro
print(err.args[0])
# uma exceção está encapsulada?
while len(err.args) == 2 and isinstance(err.args[1], BaseException):
# alteração da exceção
err = err.args[1]
# o primeiro argumento é a mensagem de erro
print(err.args[0])
Notas:
- nas linhas 25-32, na chamada main --> f1 --> f2 --> f3 (linha 30), a exceção MyError lançada por f3 será propagada até main. Será então tratada pela cláusula except da linha 31;
- linhas 61-75: na chamada main --> f4 --> f5 --> f6 (linha 62), a exceção lançada MyError pela função f6 irá propagar-se até à função main. Será então tratada pela cláusula except da linha 63. Desta vez, ao subir pela cadeia de funções chamadoras, a exceção MyError que sobe está, por sua vez, encapsulada noutra exceção;
- linhas 66-75: mostram como subir na pilha de exceções;
- linha 71: a função [isinstance(instance, Classe)] retorna True se o objeto [instance] for do tipo [Classe] ou derivado. Aqui, utilizámos a exceção de nível mais elevado [BaseException], o que nos garante a recuperação de todas as exceções;
Os resultados no ecrã são os seguintes: