异常类型
异常(Exception)是 Python 处理错误的机制。当程序遇到无法继续执行的情况时,会"抛出"一个异常对象。如果异常没有被捕获,程序会终止并打印错误信息。Python 2 内置了丰富的异常层次结构,理解这些异常类型是写出健壮代码的基础。
异常层次结构
所有内置异常都继承自 BaseException,但用户自定义异常通常继承自 Exception:
BaseException
├── SystemExit # sys.exit() 引发
├── KeyboardInterrupt # Ctrl+C 引发
├── GeneratorExit # 生成器关闭时引发
└── Exception # 所有内置非系统退出异常的基类
├── ArithmeticError
│ ├── ZeroDivisionError # 除零
│ └── OverflowError # 数值溢出
├── LookupError
│ ├── IndexError # 序列索引越界
│ └── KeyError # 字典键不存在
├── TypeError # 类型错误
├── ValueError # 值错误
├── AttributeError # 属性不存在
├── IOError # 输入输出错误
├── ImportError # 导入失败
├── NameError # 名称未定义
│ └── UnboundLocalError # 局部变量未绑定
├── SyntaxError # 语法错误
└── RuntimeError # 通用运行时错误
常见异常详解
SyntaxError:代码语法不正确,无法编译
if x > 0 # SyntaxError: invalid syntax(缺少冒号)
print x
SyntaxError 在代码编译时抛出,无法被 try-except 捕获(因为异常处理本身也是代码,需要编译后才能执行)。
NameError:使用了未定义的变量名
print undefined_var # NameError: name 'undefined_var' is not defined
TypeError:操作或函数应用于不适当类型的对象
print len(42) # TypeError: object of type 'int' has no len()
print "hello" + 5 # TypeError: cannot concatenate 'str' and 'int' objects
ValueError:操作或函数接收到具有正确类型但不适当的值
print int("hello") # ValueError: invalid literal for int() with base 10: 'hello'
IndexError:序列索引超出范围
nums = [1, 2, 3]
print nums[10] # IndexError: list index out of range
KeyError:字典中不存在指定的键
d = {"a": 1}
print d["b"] # KeyError: 'b'
AttributeError:对象没有该属性
s = "hello"
print s.append("!") # AttributeError: 'str' object has no attribute 'append'
ZeroDivisionError:除零
print 1 / 0 # ZeroDivisionError: integer division or modulo by zero
IOError:输入输出操作失败
f = open("nonexistent.txt", "r") # IOError: [Errno 2] No such file or directory
ImportError:导入模块失败
import nonexistent_module # ImportError: No module named nonexistent_module
异常对象的信息
异常对象包含错误信息,可以通过 except 捕获后访问:
try:
1 / 0
except ZeroDivisionError as e:
print type(e) # <type 'exceptions.ZeroDivisionError'>
print e # integer division or modulo by zero
print e.args # ('integer division or modulo by zero',)
e.args 是异常构造时传入的参数元组。大多数内置异常只有一个字符串参数。
异常与错误处理哲学
Python 倡导 EAFP(Easier to Ask for Forgiveness than Permission):
# EAFP:先尝试,出问题再处理
try:
value = d[key]
except KeyError:
value = default
# LBYL:先检查,再操作(Look Before You Leap)
if key in d:
value = d[key]
else:
value = default
EAFP 在 Python 中通常更快、更简洁,因为字典查找是 O(1),两次查找(in + [])比一次查找加异常处理更慢。但对于高频率的简单操作,LBYL 可能更合适。
异常的性能
异常处理本身开销很小,但抛出异常很昂贵:
import timeit
# 使用异常(不触发)
t1 = timeit.timeit('try:\n x = 1\nexcept:\n pass', number=1000000)
# 使用异常(触发)
t2 = timeit.timeit('try:\n x = 1/0\nexcept:\n pass', number=100000)
print t1, t2 # 不触发很快,触发慢约 100 倍
因此,不要用异常控制正常流程——异常应该用于"异常情况"。