|
1 | | -# 错误和异常 |
| 1 | +# 异常处理 |
| 2 | + |
| 3 | +我们在编写程序的时候,经常需要对异常情况做处理。比如,当一个数试图除以 0 时,我们需要捕获这个异常情况并做处理。你可能会使用类似 if/else 的条件语句来对异常情况做判断,比如,判断除法的分母是否为零,如果为零,则打印错误信息。 |
| 4 | + |
| 5 | +这在某些简单的情况下是可以的,但是,在大多数时候,我们应该使用 Python 的**异常处理**机制。这主要有两方面的好处: |
| 6 | + |
| 7 | +- 一方面,你可以选择忽略某些不重要的异常事件,或在需要的时候自己引发异常; |
| 8 | +- 另一方面,异常处理不会搞乱原来的代码逻辑,但如果使用一大堆 if/else 语句,不仅会没效率和不灵活,而且会让代码相当难读; |
| 9 | + |
| 10 | +# 异常对象 |
| 11 | + |
| 12 | +Python 用**异常对象(exception object)**来表示异常情况。当程序在运行过程中遇到错误时,会引发异常。如果异常对象未被处理或捕捉,程序就会用所谓的回溯(Traceback,一种错误信息)终止执行。 |
| 13 | + |
| 14 | +比如: |
| 15 | + |
| 16 | +```python |
| 17 | +>>> 1/0 |
| 18 | +Traceback (most recent call last): |
| 19 | + File "<stdin>", line 1, in <module> |
| 20 | +ZeroDivisionError: integer division or modulo by zero |
| 21 | +``` |
| 22 | + |
| 23 | +上面的 `ZeroDivisionError` 就是一个异常类,相应的异常对象就是该类的实例。Python 中所有的异常类都是从 `BaseException` 类派生的,常见的异常类型可以在[这里][1]查看。 |
| 24 | + |
| 25 | +# 使用 try/except 捕捉异常 |
| 26 | + |
| 27 | +在编写程序的时候,如果我们知道某段代码可能会导致某种异常,而又不希望程序以堆栈跟踪的形式终止,这时我们可以根据需要添加 `try/except` 或者 `try/finally` 语句(或者它们的组合)进行处理。一般来说,有以下使用形式: |
| 28 | + |
| 29 | +``` |
| 30 | +try...except... |
| 31 | +try...except...else... |
| 32 | +try...except...else...finally... |
| 33 | +try...except...except...else...finally... |
| 34 | +try...finally... |
| 35 | +``` |
| 36 | + |
| 37 | +## 基本形式 |
| 38 | + |
| 39 | +捕捉异常的基本形式是 `try...except`。 |
| 40 | + |
| 41 | +让我们看看第一个例子: |
| 42 | + |
| 43 | +```python |
| 44 | +try: |
| 45 | + x = input('Enter x: ') |
| 46 | + y = input('Enter y: ') |
| 47 | + print x / y |
| 48 | +except ZeroDivisionError as e: |
| 49 | + print 'Error:',e |
| 50 | + |
| 51 | +print 'hello world' |
| 52 | +``` |
| 53 | + |
| 54 | +当 y = 0 时,看看执行结果: |
| 55 | + |
| 56 | +```python |
| 57 | +Enter x: 3 |
| 58 | +Enter y: 0 |
| 59 | +Error: integer division or modulo by zero |
| 60 | +hello world |
| 61 | +``` |
| 62 | + |
| 63 | +可以看到,我们的程序正确捕获了`除以零`的异常,而且程序没有以堆栈跟踪的形式终止,而是继续执行后面的代码,打印出 'hello world'。 |
| 64 | + |
| 65 | +## 多个 except 子句 |
| 66 | + |
| 67 | +有时,我们的程序可能会出现多个异常,这时可以用多个 except 子句来处理这种情况。 |
| 68 | + |
| 69 | +让我们继续看第一个例子,如果 y 输入的是一个非数字的值,就会产生另外一个异常: |
| 70 | + |
| 71 | +```python |
| 72 | +Enter x: 2 |
| 73 | +Enter y: 'a' # y 的输入是一个字符 |
| 74 | +---------------------------------------------------------------------- |
| 75 | +TypeError Traceback (most recent call last) |
| 76 | +<ipython-input-209-d4666cfaefb4> in <module>() |
| 77 | + 2 x = input('Enter x: ') |
| 78 | + 3 y = input('Enter y: ') |
| 79 | +----> 4 print x / y |
| 80 | + 5 except ZeroDivisionError as e: |
| 81 | + 6 print e |
| 82 | + |
| 83 | +TypeError: unsupported operand type(s) for /: 'int' and 'str' |
| 84 | +``` |
| 85 | + |
| 86 | +可以看到,当 y 输入一个字符 'a' 之后,程序产生了一个 `TypeError` 异常,并且终止,这是因为我们的 except 子句只是捕获了 `ZeroDivisionError` 异常,为了能捕获`TypeError` 异常,我们可以再加一个 except 子句,完整代码如下: |
| 87 | + |
| 88 | +```python |
| 89 | +try: |
| 90 | + x = input('Enter x: ') |
| 91 | + y = input('Enter y: ') |
| 92 | + print x / y |
| 93 | +except ZeroDivisionError as e: # 处理 ZeroDivisionError 异常 |
| 94 | + print 'ZeroDivisionError:',e |
| 95 | +except TypeError as e: # 处理 TypeError 异常 |
| 96 | + print 'TypeError:',e |
| 97 | + |
| 98 | +print 'hello world' |
| 99 | +``` |
| 100 | + |
| 101 | +当 y 输入 'a' 时,看看执行结果: |
| 102 | + |
| 103 | +```python |
| 104 | +Enter x: 3 |
| 105 | +Enter y: 'a' |
| 106 | +TypeError: unsupported operand type(s) for /: 'int' and 'str' |
| 107 | +hello world |
| 108 | +``` |
| 109 | + |
| 110 | +## 捕捉未知异常 |
| 111 | + |
| 112 | +事实上,在编写程序的时候,我们很难预料到程序的所有异常情况。比如,对于第一个例子,我们可以预料到一个 `ZeroDivisionError` 异常,如果细心一点,也会预料到一个 `TypeError` 异常,可是,还是有一些其他情况我们没有考虑到,比如在输入 x 的时候,我们直接按回车键,这时又会引发一个异常,程序也会随之挂掉: |
| 113 | + |
| 114 | +```python |
| 115 | +Enter x: # 这里输入回车键 |
| 116 | +Traceback (most recent call last): |
| 117 | + File "<stdin>", line 2, in <module> |
| 118 | + File "<string>", line 0 |
| 119 | + |
| 120 | + ^ |
| 121 | +SyntaxError: unexpected EOF while parsing |
| 122 | +``` |
| 123 | + |
| 124 | +那么,我们应该怎么在程序捕获某些难以预料的异常呢?我们在上文说过,Python 中所有的异常类都是从 `BaseException` 类派生的,也就是说,`ZeroDivisionError`、`SyntaxError` 等都是它的子类,因此,对于某些难以预料的异常,我们就可以使用 `BaseException` 来捕获,在大部分情况下,我们也可以使用 `Exception` 来捕获,因为 `Exception` 是大部分异常的父类,可以到[这里][1]查看所有异常类的继承关系。 |
| 125 | + |
| 126 | +因此,对于第一个例子,我们可以把程序做一些修改,使其更加健壮: |
| 127 | + |
| 128 | +```python |
| 129 | +try: |
| 130 | + x = input('Enter x: ') |
| 131 | + y = input('Enter y: ') |
| 132 | + print x / y |
| 133 | +except ZeroDivisionError as e: # 捕获 ZeroDivisionError 异常 |
| 134 | + print 'ZeroDivisionError:',e |
| 135 | +except TypeError as e: # 捕获 TypeError 异常 |
| 136 | + print 'TypeError:',e |
| 137 | +except BaseException as e: # 捕获其他异常 |
| 138 | + print 'BaseException:',e |
| 139 | + |
| 140 | +print 'hello world' |
| 141 | +``` |
| 142 | + |
| 143 | +注意到,我们把 `BaseException` 写在了最后一个 except 子句。如果你把它写在了第一个 except 子句,由于 `BaseException` 是所有异常的父类,那么程序的所有异常都会被第一个 except 子句捕获。 |
| 144 | + |
| 145 | +## else 子句 |
| 146 | + |
| 147 | +我们可以在 except 子句后面加一个 else 子句。当没有异常发生时,会自动执行 else 子句。 |
| 148 | + |
| 149 | +对第一个例子,加入 else 子句: |
| 150 | + |
| 151 | +```python |
| 152 | +try: |
| 153 | + x = input('Enter x: ') |
| 154 | + y = input('Enter y: ') |
| 155 | + print x / y |
| 156 | +except ZeroDivisionError as e: |
| 157 | + print 'ZeroDivisionError:',e |
| 158 | +except TypeError as e: |
| 159 | + print 'TypeError:',e |
| 160 | +except BaseException as e: |
| 161 | + print 'BaseException:',e |
| 162 | +else: |
| 163 | + print 'no error!' |
| 164 | + |
| 165 | +print 'hello world' |
| 166 | +``` |
| 167 | + |
| 168 | +看看执行结果: |
| 169 | + |
| 170 | +``` |
| 171 | +Enter x: 6 |
| 172 | +Enter y: 2 |
| 173 | +3 |
| 174 | +no error! |
| 175 | +hello world |
| 176 | +``` |
| 177 | + |
| 178 | +## finally 子句 |
| 179 | + |
| 180 | +finally 子句不管有没有出现异常都会被执行。 |
| 181 | + |
| 182 | +看看例子: |
| 183 | + |
| 184 | +```python |
| 185 | +try: |
| 186 | + x = 1/0 |
| 187 | + print x |
| 188 | +finally: |
| 189 | + print 'DONE' |
| 190 | +``` |
| 191 | + |
| 192 | +执行结果: |
| 193 | + |
| 194 | +``` |
| 195 | +DONE |
| 196 | +Traceback (most recent call last): |
| 197 | + File "<stdin>", line 2, in <module> |
| 198 | +ZeroDivisionError: integer division or modulo by zero |
| 199 | +``` |
| 200 | + |
| 201 | +再看一个例子: |
| 202 | + |
| 203 | +```python |
| 204 | +try: |
| 205 | + x = 1/0 |
| 206 | + print x |
| 207 | +except ZeroDivisionError as e: |
| 208 | + print 'ZeroDivisionError:',e |
| 209 | +finally: |
| 210 | + print 'DONE' |
| 211 | +``` |
| 212 | + |
| 213 | +执行结果: |
| 214 | + |
| 215 | +``` |
| 216 | +ZeroDivisionError: integer division or modulo by zero |
| 217 | +DONE |
| 218 | +``` |
| 219 | + |
| 220 | +## 使用 raise 手动引发异常 |
| 221 | + |
| 222 | +有时,我们使用 except 捕获了异常,又想把异常抛出去,这时可以使用 raise 语句。 |
| 223 | + |
| 224 | +看看例子: |
| 225 | + |
| 226 | +```python |
| 227 | +try: |
| 228 | + x = input('Enter x: ') |
| 229 | + y = input('Enter y: ') |
| 230 | + print x / y |
| 231 | +except ZeroDivisionError as e: |
| 232 | + print 'ZeroDivisionError:',e |
| 233 | +except TypeError as e: |
| 234 | + print 'TypeError:',e |
| 235 | +except BaseException as e: |
| 236 | + print 'BaseException:',e |
| 237 | + raise # 使用 raise 抛出异常 |
| 238 | +else: |
| 239 | + print 'no error!' |
| 240 | + |
| 241 | +print 'hello world' |
| 242 | +``` |
| 243 | + |
| 244 | +运行上面代码,当 x 输入一个回车键时,错误会被打印出来,并被抛出: |
| 245 | + |
| 246 | +```python |
| 247 | +Enter x: # 这里输入回车键 |
| 248 | +BaseException: unexpected EOF while parsing (<string>, line 0) |
| 249 | +Traceback (most recent call last): |
| 250 | + File "<stdin>", line 2, in <module> |
| 251 | + File "<string>", line 0 |
| 252 | + |
| 253 | + ^ |
| 254 | +SyntaxError: unexpected EOF while parsing |
| 255 | +``` |
| 256 | + |
| 257 | +上面的 raise 语句是不带参数的,它会把当前错误原样抛出。事实上,我们也创建自己的异常类,并抛出自定义的异常。 |
| 258 | + |
| 259 | +**创建自定义的异常类需要从 Exception 类继承**,可以间接继承或直接继承,也就是可以继承其他的内建异常类。比如: |
| 260 | + |
| 261 | +```python |
| 262 | +# 自定义异常类 |
| 263 | +class SomeError(Exception): |
| 264 | + pass |
| 265 | + |
| 266 | +try: |
| 267 | + x = input('Enter x: ') |
| 268 | + y = input('Enter y: ') |
| 269 | + print x / y |
| 270 | +except ZeroDivisionError as e: |
| 271 | + print 'ZeroDivisionError:',e |
| 272 | +except TypeError as e: |
| 273 | + print 'TypeError:',e |
| 274 | +except BaseException as e: |
| 275 | + print 'BaseException:',e |
| 276 | + raise SomeError('invalid value') # 抛出自定义的异常 |
| 277 | +else: |
| 278 | + print 'no error!' |
| 279 | + |
| 280 | +print 'hello world' |
| 281 | +``` |
| 282 | + |
| 283 | +运行上面代码,当 x 输入一个回车键时,错误被打印出来,并抛出我们自定义的异常: |
| 284 | + |
| 285 | +``` |
| 286 | +Enter x: |
| 287 | +BaseException: unexpected EOF while parsing (<string>, line 0) |
| 288 | +---------------------------------------------------------------------- |
| 289 | +SomeError Traceback (most recent call last) |
| 290 | +<ipython-input-20-66060b472f91> in <module>() |
| 291 | + 12 except BaseException as e: |
| 292 | + 13 print 'BaseException:',e |
| 293 | +---> 14 raise SomeError('invalid value') |
| 294 | + 15 else: |
| 295 | + 16 print 'no error!' |
| 296 | +
|
| 297 | +SomeError: invalid value |
| 298 | +``` |
| 299 | + |
| 300 | +# 小结 |
| 301 | + |
| 302 | +- Python 中所有的异常类都是从 `BaseException` 类派生的。 |
| 303 | +- 通过 try/except 来捕捉异常,可以使用多个 except 子句来分别处理不同的异常。 |
| 304 | +- else 子句在主 try 块没有引发异常的情况下被执行。 |
| 305 | +- finally 子句不管是否发生异常都会被执行。 |
| 306 | +- 通过继承 Exception 类可以创建自己的异常类。 |
| 307 | + |
| 308 | +# 参考资料 |
| 309 | + |
| 310 | +- 《Python 基础教程》 |
| 311 | +- [Python 异常处理](http://www.runoob.com/python/python-exceptions.html) |
| 312 | + |
| 313 | + |
| 314 | +[1]: https://docs.python.org/2/library/exceptions.html#exception-hierarchy |
| 315 | + |
2 | 316 |
|
0 commit comments