---
tags: Python
---
# Handling Exceptions
[TOC]
`try-except` 可以決定當錯誤發生時,相對應的處理是什麼。有了錯誤處理,就可以印出錯誤,可以將錯誤拋出,或是將錯誤訊息存入 log ,都是不錯的處理方法。
```flow
try=>operation: try
exception=>condition: Exception
handle=>operation: except
else=>operation: else
finally=>operation: finally
try->exception
exception(no)->else->finally
exception(yes)->handle->finally
```
## Re-raising Exceptions
```python
try:
a = 1 / 0
except Exception as e:
print('something wrong')
raise e
```
```
Traceback (most recent call last):
File "main.py", line 5, in <module>
raise e
ZeroDivisionError: integer division or modulo by zero
```
:arrow_down: Better,traceback 所指的 exception 會在真正發生的地方
```python
try:
a = 1 / 0
except Exception as e:
print('something wrong')
raise
```
```
Traceback (most recent call last):
File "main.py", line 2, in <module>
a = 1 / 0
ZeroDivisionError: integer division or modulo by zero
```
## Exception Order
只要是 same class 或有相同的 base class ,都會算在同一個異常處理,因此涵蓋越多的 Exception 要擺越後面:
```python
class B(Exception):
pass
class C(B):
pass
class D(C):
pass
for cls in [B, C, D]:
try:
raise cls()
except D:
print("D")
except C:
print("C")
except B:
print("B")
```
```
Output: B C D
```
```python
for cls in [B, C, D]:
try:
raise cls()
except B:
print("B")
except D:
print("D")
except C:
print("C")
```
```
Output: B B B
```
## Raising Custom Exceptions
```python
def average(x, y):
if x <= 0 or y <= 0:
raise Exception('Both x and y should be positive')
return (x + y) / 2
```
```
Exception: Both x and y should be positive
```
:arrow_down: Better,這樣在別的地方 try-catch 時才好抓取特定的 Exception 。
```python
class NonPositiveError(Exception):
pass
def average(x, y):
if x <= 0 or y <= 0:
raise NonPositiveError('Both x and y should be positive')
return (x + y) / 2
try:
avg = average(1, 0)
except NonPositiveError as err:
avg = 0
print(err)
finally:
print(avg)
```
:arrow_down: More better ,當一個套件專案下的 Exception 越多,用一個最基礎的去涵蓋下面的 Exceptions 是常見的做法。
> When creating a module that can raise several distinct errors, a common practice is to create a base class for exceptions defined by that module, and subclass that to create specific exception classes for different error conditions.
```python
class Error(Exception):
pass
class NonPositiveError(IntegerError):
def __init__(self, x, y):
msg = 'Both x and y should be positive'
super().__init__(msg)
self.x = x
self.y = y
class TooBigError(IntegerError):
def __init__(self, x, y):
msg = 'Both x and y should be less than 10'
super().__init__(msg)
self.x = x
self.y = y
def average(x, y):
if x <= 0 or y <= 0:
raise NonPositiveError('Both x and y should be positive')
if x > 10 or y > 10:
raise TooBigError('Both x and y should be less than 10')
return (x + y) / 2
try:
avg = average(11, 11)
except (NonPositiveError, TooBigError) as err:
avg = 0
print(err)
finally:
print(avg)
```
## Exception Chaining
捕獲一個異常後跑出另一個異常,並且把異常的原始訊息保留下來。在不使用 `from` 時更傾向於新異常與正在處理的異常沒有關係。
Exception 裡的 `__cause__` 預設都是 `None` ,可以用來揭露原始的原因。
```python
raise EXCEPTION from CAUSE
```
:arrow_down:
```python
exc = EXCEPTION
exc.__cause__ = CAUSE
raise exc
```
### Example
```python
try:
v = {}['a']
except KeyError as e:
raise ValueError('failed')
```
```
Traceback (most recent call last):
File "main.py", line 2, in <module>
v = {}['a']
KeyError: 'a'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "main.py", line 4, in <module>
raise ValueError('failed')
ValueError: failed
```
:vs:
```python
try:
v = {}['a']
except KeyError as e:
raise ValueError('failed') from e
```
```
Traceback (most recent call last):
File "main.py", line 2, in <module>
v = {}['a']
KeyError: 'a'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "main.py", line 4, in <module>
raise ValueError('failed') from e
ValueError: failed
```
:vs:
```python
try:
v = {}['a']
except KeyError as e:
a = 1 / 0 # Unexcpted error
raise ValueError('failed') from e
```
```
Traceback (most recent call last):
File "main.py", line 2, in <module>
v = {}['a']
KeyError: 'a'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "main.py", line 4, in <module>
a = 1 / 0
ZeroDivisionError: division by zero
```
可以更知道這個 Exception 發生的原因:
1. 因為另一個 Exception 而造成,是我們所預期的情況
2. 在處理 Exception 時額外又產生的 Exception ,是我們不預期的情況
## Google Python Style Guide
1. Make use of built-in exception classes when it makes sense. For example, raise a ValueError if you were passed a negative number but were expecting a positive one. Do not use assert statements for validating argument values of a public API. assert is used to ensure internal correctness, not to enforce correct usage nor to indicate that some unexpected event occurred. If an exception is desired in the latter cases, use a raise statement.
### built-in exception:
```python
if value < 1024:
raise ValueError('Must be at least 1024, not %d.' % value)
```
```shell
traceback (most recent call last):
File "main.py", line 4, in <module>
raise ValueError('Must be at least 1024, not %d.' % value)
ValueError: Must be at least 1024, not 200.
```
### assert:
```python
assert value >= 1024, 'Must be at least 1024, not %d.' % value
```
```shell
Traceback (most recent call last):
File "main.py", line 6, in <module>
assert value >= 1024, 'Must be at least 1024, not %d.' % value
AssertionError: Must be at least 1024, not 200.
```
### 我覺得
1. 從 exception class 就可以清楚知道原因。
2. 從程式碼邏輯上也比較直覺。
2. Never use catch-all `except:` statements, or catch `Exception` or `StandardError` . except: will really catch everything including misspelled names, sys.exit() calls, Ctrl+C interrupts, unittest failures and all kinds of other exceptions that you simply don’t want to catch.
**處理例外不要什麼都不做,也不要人人好**。不推薦使用 `except:` 或 `except Exception:` 這種涵蓋太多其他 Exception 的方式,因為捕捉到的異常太多,要找原因不好找。涵蓋的範圍可以看 [Exception hierarchy](https://docs.python.org/3/library/exceptions.html#exception-hierarchy) 。
以下就是個無法 Ctrl+C 的悲劇,最慘的是他還沒有任何處理:
```python
while True:
try:
s = input()
except:
pass
```
**Catching Specific Exceptions**,只處理已知的情況
```python
try:
file = open('my_file.dat') # FileNotFoundError
data = file.readfile() # AttributeError
print('Data Loaded')
except:
file = open('my_file.dat', 'w')
print('File created')
```
:arrow_down:
```python
try:
file = open('my_file.dat') # FileNotFoundError
data = file.readfile() # AttributeError
print('Data Loaded')
except FileNotFoundError:
file = open('my_file.dat', 'w')
print('File created')
```
如果沒有指定好 Exception ,就會很難發現我們預想之外的錯誤,給予預期的 Exception 可以避免這個問題,像是下方在第二次執行時,才會正確地跳出錯誤:
```
AttributeError: '_io.TextIOWrapper' object has no attribute 'readfile'
```
3. Minimize the amount of code in a `try` / `except` block. The larger the body of the `try`, the more likely that an exception will be raised by a line of code that you didn’t expect to raise an exception. In those cases, the `try` / `except` block hides a real error.
以下是要去開啟一的檔案抓取另一個檔案名稱,再去開啟新檔案的程式。如果 `try` 的範圍太大,遇到相同的 Exception 時處理上會遇到問題。
```python
try:
file = open(filename)
new_filename = file.readline()
new_file = open(new_filename)
data = new_file.read()
except FileNotFoundError:
print('File not found, creating one')
file = open(filename, 'w')
```
## 資料來源
1. [Errors and Exceptions / Python](https://docs.python.org/3/tutorial/errors.html)
1. [Built-in Exceptions / Python](https://docs.python.org/3/library/exceptions.html)
1. [Google Python Style Guide / Google](http://google.github.io/styleguide/pyguide.html#24-exceptions)
2. [Fail fast / Jim Shore](https://martinfowler.com/ieeeSoftware/failFast.pdf)