Skip to content

5. As exceções

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

Image

5.1. script [exceptions_01]

O primeiro script ilustra a necessidade de gerir as exceções.

# provoca-se deliberadamente um erro
x = 4 / 0

Provocamos deliberadamente um erro para ver o que acontece (exceptions-01.py):

1
2
3
4
5
6
7
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/exceptions/exceptions_01.py
Traceback (most recent call last):
  File "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/exceptions/exceptions_01.py", line 2, in <module>
    x = 4 / 0
ZeroDivisionError: division by zero

Process finished with exit code 1

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:

C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/exceptions/exceptions_02.py
Traceback (most recent call last):
  File "C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/exceptions/exceptions_02.py", line 136, in <module>
    x = 4 / 0
ZeroDivisionError: division by zero
essai n° 0 : division by zero
x=2
essai n° 1 : division by zero
essai n° 2 : (Exception) division by zero
essai n° 3 : (ZeroDivisionError) division by zero
essai n° 4 : il y a eu un problème
essai n° 5 : invalid literal for int() with base 10: 'x'
essai n° 6 : invalid literal for int() with base 10: 'x', paramètres=("invalid literal for int() with base 10: 'x'",)
essai n° 7 : ('param1', 'param2', 'param3'), paramètres=('param1', 'param2', 'param3')
essai n° 8 : ('info1', 'info2', 'info3'), paramètres=('info1', 'info2', 'info3')
essai n° 9 : mon msg d'erreur
essai n° 10 : finally x=1
essai n° 11 : exception
essai n° 11 : finally x=None
essai n° 12 : finally x=None

Process finished with exit code 1

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:

1
2
3
4
5
6
7
8
C:\Data\st-2020\dev\python\cours-2020\python3-flask-2020\venv\Scripts\python.exe C:/Data/st-2020/dev/python/cours-2020/python3-flask-2020/exceptions/exceptions_03.py
type : <class '__main__.MyError'>, arguments : ('exception dans f3',)
type : <class '__main__.MyError'>, arguments : ('exception dans f4', MyError('exception dans f5', MyError('exception dans f6')))
exception dans f4
exception dans f5
exception dans f6

Process finished with exit code 0