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

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):
| 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:
| 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
|