简介
和其他的语言一样,Python 中也有异常和错误。在 Python 中,所有异常都是 BaseException
的类的实例。 今天我们来详细看一下 Python 中的异常和对他们的处理方式。
Python 中的内置异常类
Python 中所有异常类都来自 BaseException,它是所有内置异常的基类。
虽然它是所有异常类的基类,但是对于用户自定义的类来说,并不推荐直接继承 BaseException,而是继承 Exception.
先看下 Python 中异常类的结构关系:
BaseException
+-- SystemExit
+-- KeyboardInterrupt
+-- GeneratorExit
+-- Exception
+-- StopIteration
+-- StopAsyncIteration
+-- ArithmeticError
| +-- FloatingPointError
| +-- OverflowError
| +-- ZeroDivisionError
+-- AssertionError
+-- AttributeError
+-- BufferError
+-- EOFError
+-- ImportError
| +-- ModuleNotFoundError
+-- LookupError
| +-- IndexError
| +-- KeyError
+-- MemoryError
+-- NameError
| +-- UnboundLocalError
+-- OSError
| +-- BlockingIOError
| +-- ChildProcessError
| +-- ConnectionError
| | +-- BrokenPipeError
| | +-- ConnectionAbortedError
| | +-- ConnectionRefusedError
| | +-- ConnectionResetError
| +-- FileExistsError
| +-- FileNotFoundError
| +-- InterruptedError
| +-- IsADirectoryError
| +-- NotADirectoryError
| +-- PermissionError
| +-- ProcessLookupError
| +-- TimeoutError
+-- ReferenceError
+-- RuntimeError
| +-- NotImplementedError
| +-- RecursionError
+-- SyntaxError
| +-- IndentationError
| +-- TabError
+-- SystemError
+-- TypeError
+-- ValueError
| +-- UnicodeError
| +-- UnicodeDecodeError
| +-- UnicodeEncodeError
| +-- UnicodeTranslateError
+-- Warning
+-- DeprecationWarning
+-- PendingDeprecationWarning
+-- RuntimeWarning
+-- SyntaxWarning
+-- UserWarning
+-- FutureWarning
+-- ImportWarning
+-- UnicodeWarning
+-- BytesWarning
+-- ResourceWarning
复制代码
其中 BaseException,Exception,ArithmeticError,BufferError,LookupError 主要被作为其他异常的基类。
语法错误
在 Python 中,对于异常和错误通常可以分为两类,第一类是语法错误,又称解析错误。也就是代码还没有开始运行,就发生的错误。
其产生的原因就是编写的代码不符合 Python 的语言规范:
>>> while True print('Hello world')
File "<stdin>", line 1
while True print('Hello world')
^
SyntaxError: invalid syntax
复制代码
上面代码原因是 print 前面少了 冒号。
异常
即使我们的程序符合 python 的语法规范,但是在执行的时候,仍然可能发送错误,这种在运行时发送的错误,叫做异常。
看一下下面的异常:
>>> 10 * (1/0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
>>> 4 + spam*3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'spam' is not defined
>>> '2' + 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't convert 'int' object to str implicitly
复制代码
异常处理
程序发生了异常之后该怎么处理呢?
我们可以使用 try except 语句来捕获特定的异常。
>>> while True:
... try:
... x = int(input("Please enter a number: "))
... break
... except ValueError:
... print("Oops! That was no valid number. Try again...")
...
复制代码
上面代码的执行流程是,首先执行 try 中的子语句,如果没有异常发生,那么就会跳过 except,并完成 try 语句的执行。
如果 try 中的子语句中发生了异常,那么将会跳过 try 子句中的后面部分,进行 except 的异常匹配。如果匹配成功的话,就会去执行 except 中的子语句。
如果发生的异常和 except 子句中指定的异常不匹配,则将其传递到外部的 try
语句中。
一个 try 中可以有多个 except 子句,我们可以这样写:
try:
raise cls()
except D:
print("D")
except C:
print("C")
except B:
print("B")
复制代码
一个 except 也可以带多个异常:
... except (RuntimeError, TypeError, NameError):
... pass
复制代码
except 子句还可以省略异常名,用来匹配所有的异常:
import sys
try:
f = open('myfile.txt')
s = f.readline()
i = int(s.strip())
except OSError as err:
print("OS error: {0}".format(err))
except ValueError:
print("Could not convert data to an integer.")
except:
print("Unexpected error:", sys.exc_info()[0])
raise
复制代码
try
… except
语句有一个可选的 else 子句,在使用时必须放在所有的 except 子句后面。对于在 try 子句不引发异常时必须执行的代码来说很有用。 例如:
for arg in sys.argv[1:]:
try:
f = open(arg, 'r')
except OSError:
print('cannot open', arg)
else:
print(arg, 'has', len(f.readlines()), 'lines')
f.close()
复制代码
except 可以指定异常变量的名字 instance
,这个变量代表这个异常实例。
我们可以通过 instance.args 来输出异常的参数。
同时,因为异常实例定义了 __str__()
,所以可以直接使用 print 来输出异常的参数。而不需要使用 .args
。
我们看一个例子:
>>> try:
... raise Exception('spam', 'eggs')
... except Exception as inst:
... print(type(inst)) # the exception instance
... print(inst.args) # arguments stored in .args
... print(inst) # __str__ allows args to be printed directly,
... # but may be overridden in exception subclasses
... x, y = inst.args # unpack args
... print('x =', x)
... print('y =', y)
...
<class 'Exception'>
('spam', 'eggs')
('spam', 'eggs')
x = spam
y = eggs
复制代码
上面的例子中,我们在 try 字句中抛出了一个异常,并且指定了 2 个参数。
抛出异常
我们可以使用 raise 语句来抛出异常。
>>> raise NameError('HiThere')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: HiThere
复制代码
raise 的参数是一个异常,这个异常可以是异常实例或者是一个异常类。
注意,这个异常类必须是Exception
的子类。
如果传递的是一个异常类,那么将会调用无参构造函数来隐式实例化:
raise ValueError # shorthand for 'raise ValueError()'
复制代码
如果我们捕获了某些异常,但是又不想去处理,那么可以在 except 语句中使用 raise,重新抛出异常。
>>> try:
... raise NameError('HiThere')
... except NameError:
... print('An exception flew by!')
... raise
...
An exception flew by!
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
NameError: HiThere
复制代码
异常链
如果我们通过 except 捕获一个异常 A 之后,可以通过 raise 语句再次抛出一个不同的异常类型 B。
那么我们看到的这个异常信息就是 B 的信息。但是我们并不知道这个异常 B 是从哪里来的,这时候,我们就可以用到异常链。
异常链就是抛出异常的时候,使用 raise from 语句:
>>> def func():
... raise IOError
...
>>> try:
... func()
... except IOError as exc:
... raise RuntimeError('Failed to open database') from exc
...
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "<stdin>", line 2, in func
OSError
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
RuntimeError: Failed to open database
复制代码
上面的例子中,我们在捕获 IOError 之后,又抛出了 RuntimeError,通过使用异常链,我们很清晰的看出这两个异常之间的关系。
默认情况下,如果异常是从 except 或者 finally 中抛出的话,会自动带上异常链信息。
如果你不想带上异常链,那么可以 from None
。
try:
open('database.sqlite')
except IOError:
raise RuntimeError from None
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
RuntimeError
复制代码
自定义异常
用户可以继承 Exception 来实现自定义的异常,我们看一些自定义异常的例子:
class Error(Exception):
"""Base class for exceptions in this module."""
pass
class InputError(Error):
"""Exception raised for errors in the input.
Attributes:
expression -- input expression in which the error occurred
message -- explanation of the error
"""
def __init__(self, expression, message):
self.expression = expression
self.message = message
class TransitionError(Error):
"""Raised when an operation attempts a state transition that's not
allowed.
Attributes:
previous -- state at beginning of transition
next -- attempted new state
message -- explanation of why the specific transition is not allowed
"""
def __init__(self, previous, next, message):
self.previous = previous
self.next = next
self.message = message
复制代码
finally
try 语句可以跟着一个 finally 语句来实现一些收尾操作。
>>> try:
... raise KeyboardInterrupt
... finally:
... print('Goodbye, world!')
...
Goodbye, world!
KeyboardInterrupt
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
复制代码
finally
子句将作为 try
语句结束前的最后一项任务被执行, 无论 try 中是否产生异常,finally 语句中的代码都会被执行。
如果 finally
子句中包含一个 return
语句,则返回值将来自 finally
子句的某个 return
语句的返回值,而非来自 try
子句的 return
语句的返回值。
>>> def bool_return():
... try:
... return True
... finally:
... return False
...
>>> bool_return()
False
复制代码
本文已收录于 http://www.flydean.com/09-python-error-exception/
最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!
欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!
评论