Skip to content

5. As exceções

Vamos nos concentrar agora nas exceções.

Image

5.1. script [exceptions_01]

O primeiro script ilustra a necessidade de lidar com exceções.

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

Provocamos um erro propositalmente 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 nos mostra:

  • o tipo da exceção: ZeroDivisionError;
  • a mensagem de erro associada: divisão por zero. Ela está em inglês. Isso é algo que talvez queiramos alterar.

Uma regra essencial é que devemos fazer de tudo para evitar as exceções geradas pelo interpretador Python. Precisamos lidar com os erros por conta própria.

A sintaxe para o tratamento 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 continua 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 houver outra cláusula [except], então o mesmo processo se repete. E assim por diante…;
  • pode haver quantas cláusulas [except] forem necessárias para lidar com 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], ela será repassada para o código chamador. Se este estiver, por sua vez, dentro de uma estrutura try/except, a exceção será novamente tratada; caso contrário, ela continuará subindo pela cadeia de métodos chamados. Em última instância, ela chega ao interpretador Python. Este, então, interrompe o programa em execução e exibe uma mensagem de erro do tipo mostrado no exemplo anterior. A regra é, portanto, que o programa principal deve interceptar todas as exceções que possam ser propagadas pelos métodos chamados;
  • linha 7: a cláusula [finally] é sempre executada, independentemente de ter ocorrido uma exceção (continuação do except**) ou não (continuação do try**). Isso é válido mesmo que tenha ocorrido uma exceção e ela não tenha sido interceptada. Nesse caso, a cláusula [finally] será executada antes que a exceção seja propagada para o código chamador;

Uma exceção traz consigo informações sobre o erro que ocorreu. É possível obtê-las com a seguinte sintaxe:


except MyException as exception:

[exception] é a exceção que ocorreu. [exception.args] representa a tupla de 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 interceptação de exceções com a sintaxe [ex.args], se [ex] for a exceção interceptada pela cláusula [except].

Esses conceitos são ilustrados pelo script a seguir.

5.2. script [exceptions_02]

O script a seguir lida explicitamente com os erros:


# gestão de exceções
essai = 0

# provoca-se um erro e ele é 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 mudou
print(f"x={x}")

# recomeçamos
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 é interrompida no primeiro [except] capaz de tratar a exceção
essai += 1
try:
    x = 4 / 0
except ValueError as erreur:
    # essa 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:
    # essa 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 interessamos pela 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 em número inteiro
    x = int("x")
except ValueError as erreur:
    # erro é a exceção interceptada
    print(f"essai n° {essai} : {erreur}")

# uma exceção transporta informações em uma 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 suas próprias exceções
# elas devem ser derivadas 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ça-se 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
# haja ou não exceção
essai += 1
x = None
try:
    x = 1
except:
    # exceção
    print(f"essai n° {essai} : exception")
finally:
    # executado 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:
    # isso gera 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 ela é exibida;
  • linha 12: devido à exceção ocorrida, x não recebeu nenhum valor na linha 7 e, portanto, não alterou seu valor;
  • 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: inserimos várias cláusulas except para lidar com vários tipos de exceção. Apenas uma cláusula except será executada, ou nenhuma, se a exceção não atender a nenhuma cláusula “except”;
  • linhas 38-50: repetimos o mesmo procedimento, alterando a ordem das cláusulas [except] para demonstrar a função delas;
  • linhas 52-58: a cláusula except pode não ter nenhum argumento. Nesse caso, ela 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 maneira de lançar (raise) uma exceção;
  • linhas 86-106: ilustram o uso de uma classe de exceção proprietária MyError. A classe MyError limita-se a derivar da classe base BaseException. Ela não acrescenta nada à sua classe base. Mas agora ela pode ser nomeada explicitamente nas cláusulas except;
  • linhas 108-130: ilustram o uso da cláusula finally;
  • linhas 132-139: essas linhas mostram que a cláusula [except] não é obrigatória;

Os resultados na tela 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 de 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 há tratamento de exceções — elas são repassadas automaticamente
    return f2(x)


def f2(y: int) -> int:
    # não há tratamento de exceções — elas 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 sobem pela cadeia de métodos chamados
# até que um método as intercepte. Aqui, 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, a relançamos
        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])

Observações:

  • linhas 25-32, na chamada main --> f1 --> f2 --> f3 (linha 30), a exceção MyError lançada por f3 será propagada até main. Ela 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 será propagada até main. Ela 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 está sendo propagada está, por sua vez, encapsulada em outra exceção;
  • linhas 66-75: mostram como subir pela 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, utilizamos a exceção de nível mais alto, [BaseException], o que nos garante a recuperação de todas as exceções;

Os resultados na tela 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