Skip to content

5. Exceções

Vamos agora voltar a nossa atenção para as exceções.

Image

5.1. script [exceptions_01]

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

# on provoque volontairement une erreur
x = 4 / 0

Provocamos intencionalmente 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 dá-nos:

  • o tipo de exceção: ZeroDivisionError;
  • a mensagem de erro associada: divisão por zero. Está em inglês. Talvez queira alterar isto.

Uma regra fundamental é que devemos fazer tudo o que for possível para evitar exceções geradas pelo interpretador Python. Temos de tratar dos erros nós próprios.

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). Neste 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 na linha 4 são executadas;
  • linha 5: se a exceção não foi capturada pela linha 3 e existir outra cláusula [except], então ocorre o mesmo processo. Etc…;
  • pode haver tantas cláusulas [except] quantas forem necessárias para lidar com os diferentes tipos de exceções que possam ocorrer dentro do bloco [try];
  • se a exceção não tiver sido tratada por nenhuma das cláusulas [except], então propagar-se-á de volta para o código de chamada. Se o código de chamada estiver ele próprio dentro de uma estrutura try/except, a exceção é tratada novamente; caso contrário, continua a propagar-se pela cadeia de métodos chamados. Por fim, chega ao interpretador Python. O interpretador encerra então o programa em execução e apresenta uma mensagem de erro do tipo mostrado no exemplo anterior. A regra é, portanto, que o programa principal deve capturar todas as exceções que possam propagar-se a partir dos métodos chamados;
  • linha 7: a cláusula [finally] é sempre executada, quer tenha ocorrido uma exceção (a seguir ao except) quer não (a seguir ao try). Isto é verdade mesmo que tenha ocorrido uma exceção e esta não tenha sido capturada. Neste caso, a cláusula [finally] será executada antes de a exceção se propagar de volta para o código de chamada;

Uma exceção contém informações sobre o erro que ocorreu. Estas informações podem ser obtidas utilizando a seguinte sintaxe:


except MyException as exception:

[exception] é a exceção que ocorreu. [exception.args] representa a tupla dos parâmetros da exceção.

Para lançar uma exceção, utilize a sintaxe


raise MyException(param1, param2…)

onde MyException é, na maioria das vezes, uma classe derivada da classe BaseException. Os parâmetros passados ao construtor da classe estarão disponíveis para a cláusula except das estruturas de tratamento de exceções utilizando a sintaxe [ex.args] se [ex] for a exceção capturada pela cláusula [except].

Estes conceitos são ilustrados pelo seguinte script.

5.2. script [exceptions_02]

O script seguinte trata explicitamente os erros:

#  exception handling
essai = 0

#  cause an error and manage it
x = 2
try:
    x = 4 / 0
except ZeroDivisionError as erreur:
    #  error is the exception intercepted
    print(f"essai n° {essai} : {erreur}")
#  the value of x has not changed
print(f"x={x}")

#  here we go again
essai += 1
try:
    x = 4 / 0
except BaseException as erreur:
    #  we intercept the most general exception
    #  error is the exception intercepted
    print(f"essai n° {essai} : {erreur}")

#  different types of exceptions can be intercepted
#  execution stops on the 1st [except] able to handle the exception
essai += 1
try:
    x = 4 / 0
except ValueError as erreur:
    #  this exception does not occur here
    print(f"essai n° {essai} : {erreur}")
except BaseException as erreur:
    #  we intercept the most general exception
    print(f"essai n° {essai} : (Exception) {erreur}")
except ZeroDivisionError as erreur:
    #  we intercept a specific type
    print(f"essai n° {essai} : (ZeroDivisionError) {erreur}")

#  start again, changing the order 
essai += 1
try:
    x = 4 / 0
except ValueError as erreur:
    #  this exception does not occur here
    print(f"essai n° {essai} : {erreur}")
except ZeroDivisionError as erreur:
    #  we intercept a specific type
    print(f"essai n° {essai} : (ZeroDivisionError) {erreur}")
except BaseException as erreur:
    #  we intercept the most general exception
    print(f"essai n° {essai} : (Exception) {erreur}")

#  an except clause with no arguments
essai += 1
try:
    x = 4 / 0
except:
    #  we're not interested in the nature of the exception
    print(f"essai n° {essai} : il y a eu un problème")

#  another type of exception
essai += 1
try:
    #  x cannot be converted to an integer
    x = int("x")
except ValueError as erreur:
    #  error is the exception intercepted
    print(f"essai n° {essai} : {erreur}")

#  an exception transports information in a tuple accessible to the program
essai += 1
try:
    x = int("x")
except ValueError as erreur:
    #  error is the exception intercepted
    print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")

#  exceptions can be thrown
essai += 1
try:
    raise ValueError("param1", "param2", "param3")
except ValueError as erreur:
    #  error is the exception intercepted
    print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")


#  you can create your own exceptions
#  they must derive from class [BaseException]
class MyError(BaseException):
    pass


#  throw MyError exception
essai += 1
try:
    raise MyError("info1", "info2", "info3")
except MyError as erreur:
    #  error is the exception intercepted
    print(f"essai n° {essai} : {erreur}, paramètres={erreur.args}")

#  throw MyError exception with error msg
essai += 1
try:
    raise MyError("mon msg d'erreur")
except MyError as erreur:
    #  error is the exception intercepted
    print(f"essai n° {essai} : {erreur.args[0]}")

#  the finally clause is always executed
#  whether or not there is an exception
essai += 1
x = None
try:
    x = 1
except:
    #  exception
    print(f"essai n° {essai} : exception")
finally:
    #  executed in all cases
    print(f"essai n° {essai} : finally x={x}")

essai += 1
x = None
try:
    x = 2 / 0
except:
    #  exception
    print(f"essai n° {essai} : exception")
finally:
    #  executed in all cases
    print(f"essai n° {essai} : finally x={x}")

#  we don't have to include an except clause
essai += 1
try:
    #  we cause an error
    x = 4 / 0
finally:
    #  executed in all cases
    print(f"essai n° {essai} : finally x={x}")

Notas:

  • linhas 4–12: tratam uma divisão por zero;
  • linha 8: capturamos a exceção exata que ocorre e exibimo-la;
  • linha 12: devido à exceção que ocorreu, x não recebeu um valor na linha 7 e, portanto, não alterou o seu valor;
  • linhas 14–21: fazemos o mesmo, mas capturamos uma exceção de nível superior do tipo BaseException. Como a exceção ZeroDivisionError é uma subclasse de BaseException, a cláusula except irá capturá-la;
  • linhas 23–36: usamos 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 corresponder a nenhuma cláusula except;
  • linhas 38–50: Fazemos o mesmo, alterando a ordem das cláusulas [except] para demonstrar o seu papel;
  • linhas 52–58: A cláusula `except` pode não ter argumentos. Neste caso, ela captura todas as exceções;
  • linhas 60–67: apresentamos a exceção ValueError;
  • linhas 69–75: recuperamos a informação contida na exceção;
  • linhas 77–83: apresentam como lançar uma exceção;
  • linhas 86–106: ilustram a utilização de uma classe de exceção personalizada, MyError. A classe MyError herda simplesmente da classe base BaseException. Não acrescenta nada à sua classe base. No entanto, pode agora ser explicitamente nomeada nas cláusulas `except`;
  • linhas 108–130: ilustram o uso da cláusula finally;
  • linhas 132–139: estas linhas mostram que a cláusula [except] não é obrigatória;

A saída no ecrã é a seguinte:

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 como as exceções são propagadas ao longo da cadeia de funções de chamada:

#  a proprietary exception
class MyError(BaseException):
    pass


#  three functions
def f1(x: int) -> int:
    #  exceptions are not handled - they are automatically raised
    return f2(x)


def f2(y: int) -> int:
    #  exceptions are not handled - they are automatically raised
    return f3(y)


def f3(z: int) -> int:
    if (z % 2) == 0:
        #  if z is even, throw an exception
        raise MyError("exception dans f3")
    else:
        return 2 * z


#  ---------- hand

#  exceptions go up the chain of methods called
#  until a method intercepts it. In this case, it will be main
try:
    print(f1(4))
except MyError as erreur:
    print(f"type : {type(erreur)}, arguments : {erreur.args}")


#  three other functions that enrich the exceptions it tracks
def f4(x: int) -> int:
    try:
        return f5(x)
    except MyError as erreur:
        #  we enrich the exception then relaunch it
        raise MyError("exception dans f4", erreur)


def f5(y: int) -> int:
    try:
        return f6(y)
    except MyError as erreur:
        #  we enrich the exception then relaunch it
        raise MyError("exception dans f5", erreur)


def f6(z: int) -> int:
    if (z % 2) == 0:
        #  throw an exception if z is even
        raise MyError("exception dans f6")
    else:
        return 2 * z


#  ---------- hand
try:
    print(f4(4))
except MyError as erreur:
    #  exception display
    print(f"type : {type(erreur)}, arguments : {erreur.args}")
    #  you can move up the exception stack
    err = erreur
    #  error msg is displayed
    print(err.args[0])
    #  is an exception encapsulated?
    while len(err.args) == 2 and isinstance(err.args[1], BaseException):
        #  exception change
        err = err.args[1]
        #  the 1st argument is the error msg
        print(err.args[0])

Notas:

  • linhas 25–32: na chamada main → f1 → f2 → f3 (linha 30), a exceção MyError lançada por f3 propagar-se-á até main. Será então tratada pela cláusula except na linha 31;
  • linhas 61–75: na chamada main → f4 → f5 → f6 (linha 62), a exceção MyError lançada por f6 propagar-se-á até main. Será então tratada pela cláusula except na linha 63. Desta vez, à medida que se propaga pela cadeia de funções de chamada, a exceção MyError que se propaga está ela própria encapsulada dentro de outra exceção;
  • linhas 66–75: mostram como rastrear a pilha de exceções;
  • linha 71: a função [isinstance(instance, Class)] retorna True se o objeto [instance] for do tipo [Class] ou de uma subclasse. Aqui, utilizámos a exceção de nível mais alto [BaseException], o que garante que capturamos todas as exceções;

A saída no ecrã é a seguinte:

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