标签:
?? 什么是异常?
?? Python 中的异常
?? 探测和处理异常
?? 上下文管理
?? 引发异常
?? 断言
?? 标准异常
?? 创建异常
?? 相关模块
10.1 什么是异常人们需要一个"柔和"的处理错误的方法, 而不是终止程序.
错误
从软件方面来说, 错误是语法或是逻辑上的.
当 Python 检测到一个错误时, 解释器就会指出当前流已经无法继续执行下去. 这时候就出现了异常.
语法错误
语法错误指示软件的结构上有错误, 导致不能被解释器解释或编译器无法编译.
逻辑错误(域错误和范围错误)
逻辑错误可能是由不完整或是不合法的输入所致; 在其他情况下, 还可能是逻辑无法生成, 计算, 或是输出结果需要的过程无法执行.
异常
当 Python 检测到一个错误时, 解释器就会指出当前流已经无法继续执行下去. 这时候就出现了异常.
异常是因为程序出现了错误而在正常控制流以外采取的行为.
两个阶段:
引起异常发生的错误阶段
检测(和采取可能的措施)阶段
程序员不仅仅有了检测错误的能力, 还可以在它们发生时采取更可靠的补救措 施.
由于有了运行时管理错误的能力, 应用程序的健壮性有了很大的提高.
和其他支 持异常处理的语言类似, Python 采用了 "try/尝试" 块和 "catching/捕获" 块的概念.
10.2 Python 中的异常
所有错误, 无论是语意上的还是逻辑上的,都是由于和 Python 解释器不相容导致的, 其后果就是引发异常.
traceback/跟踪返回消息
traceback/跟踪返回消息提供了一个一致的错误接口,包括错误的名称, 原因, 以及发生错误的行号.
几个异常的例子
NameError: 尝试访问一个未申明的变量
>>> foo
Traceback (innermost last): File "<stdin>", line 1, in ?
NameError: name ‘foo‘ is not defined
ZeroDivisionError: 除数为零
>>> 1 / 0
Traceback (innermost last): File "<stdin>", line 1, in ?
ZeroDivisionError: integer division or modulo by zero
SyntaxError: Python 解释器语法错误(SyntaxError 异常是唯一不是在运行时发生的异常)
>>> for
File "<string>", line 1
for
^
SyntaxError: invalid syntax
IndexError:请求的索引超出序列范围
>>> list()[0]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
>>>
KeyError:请求一个不存在的字典关键字
>>> aDict = {‘host‘: ‘earth‘, ‘port‘: 80}
>>> print aDict[‘server‘] Traceback (innermost last):
File "<stdin>", line 1, in ? KeyError: server
IOError: 输入/输出错误
>>> f = open("blah")
Traceback (innermost last):
File "<stdin>", line 1, in ?
IOError: [Errno 2] No such file or directory: ‘blah‘
AttributeError: 尝试访问未知的对象属性
>>> class myClass(object):
... pass
...
>>> myInst = myClass()
>>> myInst.bar = ‘spam‘
>>> myInst.bar
‘spam‘
>>> myInst.foo
Traceback (innermost last): File "<stdin>", line 1, in ?
AttributeError: foo
10.3 检测和处理异常
异常可以通过 try 语句来检测.
任何在 try 语句块里的代码都会被监测, 检查有无异常发生.
try 语句有两种主要形式: try-except 和 try-finally .
一个 try 语句可以对应一个或多个 except 子句, 但只能对应一个finally 子句, 或是一个 try-except-finally 复合语句.
一个可选的 else 子句可以处理没 有探测到异常的时执行的代码.
try-except 语句
try-except 语句(以及其更复杂的形式)定义了进行异常监控的一段代码, 并且提供了处理异 常的机制.
try:
try_suite # watch for exceptions here 监控这里的异常
except Exception[, reason]:
except_suite # exception-handling code 异常处理代码
在程序运行时, 解释器尝试执行 try 块里的所有代码, 如果代码块完成后没有异常发生, 执行流就会忽略 except 语句继续执行.
而当 except 语句所指定的异常发生后, 我们保存了错误的原因, 控制流立即跳转到对应的处理器( try 子句的剩余语句将被忽略).
核心笔记: 忽略代码, 继续执行, 和向上移交
try 语句块中异常发生点后的剩余语句永远不会到达(所以也永远不会执行). 一旦一个异常被
引发, 就必须决定控制流下一步到达的位置. 剩余代码将被忽略, 解释器将搜索处理器, 一旦找到,
就开始执行处理器中的代码.
如果没有找到合适的处理器, 那么异常就向上移交给调用者去处理, 这意味着堆栈框架立即回
到之前的那个. 如果在上层调用者也没找到对应处理器, 该异常会继续被向上移交, 直到找到合适
处理器. 如果到达最顶层仍然没有找到对应处理器, 那么就认为这个异常是未处理的, Python 解释
器会显示出跟踪返回消息, 然后退出.
封装内建函数
float() 内建函数的基本作用是把任意一个数值类型转换为一个浮点数.
从 Python 1.5 开始, float() 增加了把字符串表示的数值转换为浮点数的功能, 没必要使用 string 模块中的 atof() 函数.
如果你使用的老版本的 Python , 请使用 string.atof() 替换这里的 float() .
>>> float(‘foo‘)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: could not convert string to float: foo
>>> float((‘foo‘,‘bar‘))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: float() argument must be a string or a number
>>> def safe_float(obj):
... try:
... retval = float(obj)
... except ValueError:
... retval = ‘could not convert non-number to float‘
... except TypeError:
... retval = ‘obj must be a string or a number‘
... return retval
...
>>> safe_float(1)
1.0
>>> safe_float(‘foo‘)
‘could not convert non-number to float‘
>>> safe_float((‘foo‘,‘bar‘))
‘obj must be a string or a number‘
>>>
带有多个 except 的 try 语句
except Exception1[, reason1]:
suite_for_exception_Exception1
except Exception2[, reason2]:
suite_for_exception_Exception2
处理多个异常的 except 语句
except (Exc1[, Exc2[, ... ExcN]])[, reason]: #元组: except 语句在处理多个异常时要求异常被放 在一个元组
suite_for_exceptions_Exc1_to_ExcN
捕获所有异常
关于捕获所有异常, 你应当知道有些异常不是由于错误条件引起的, 它们是 SystemExit 和 KeyboardInterupt .
SystemExit 是由于当前 Python 应用程序需要退出, KeyboardInterupt 代表用户按下了 CTRL-C (^C) , 想要关闭 Python .
- BaseException
|- KeyboardInterrupt
|- SystemExit
|- Exception
|- (all other current built-in exceptions) 所有当前内建异常
BaseException 所有异常的基类
如果你确实需要捕获所有异常, 那么你就得使用新的 BaseException, 当然, 也可以使用不被推荐的裸except 语句.
try:
:
except BaseException, e:
# handle all errors
Exception常规错误的基类
try:
:
except Exception, e:
# handle real errors
核心风格: 不要处理并忽略所有错误
Python 提供给程序员的 try-except 语句是为了更好地跟踪潜在的错误并在代码里准备好处
理异常的逻辑. 这样的机制在其他语言(例如 C ) 是很难实现的. 它的目的是减少程序出错的次数
并在出错后仍能保证程序正常执行. 作为一种工具而言, 只有正确得当地使用它, 才能使其发挥作
用.
一个不正确的使用方法就是把它作为一个大绷带"绑定"到一大片代码上. 也就是说把一大段程
序(如果还不是整个程序源代码的话)放入一个 try 块中, 再用一个通用的 except 语句 "过滤"
掉任何致命的错误, 忽略它们.
# this is really bad code
try:
large_block_of_code # bandage of large piece of code
except Exception: # same as except:
pass # blind eye ignoring all errors
很明显, 错误无法避免, try-except 的作用是提供一个可以提示错误或处理错误的机制, 而不
是一个错误过滤器. 上边这样的结构会忽略许多错误, 这样的用法是缺乏工程实践的表现, 我们
不赞同这样做.
底线: 避免把大片的代码装入 try-except 中然后使用 pass 忽略掉错误. 你可以捕获特定
的异常并忽略它们, 或是捕获所有异常并采取特定的动作. 不要捕获所有异常,然后忽略掉它们.
异常也可以有参数, 异常引发后它会被传递给异常处理器.
当异常被引发后参数是作为附加帮助信息传递给异常处理器的.
异常原因是可选的, 但标准内建异常提供至少一个参数, 指示异常原因的一个字符串.
<type ‘exceptions.TypeError‘>
>>> try:
... float({})
... except TypeError,diag:
... pass
...
>>> str(diag)
‘float() argument must be a string or a number‘
>>> type(diag)
<type ‘exceptions.TypeError‘>
>>> print diag
float() argument must be a string or a number
>>> diag.__class__
<type ‘exceptions.TypeError‘>
>>> diag.__doc__
‘Inappropriate argument type.‘
>>>
核心风格: 遵循异常参数规范
当你在自己的代码中引发内建(built-in)的异常时, 尽量遵循规范, 用和已有 Python 代码
一致错误信息作为传给异常的参数元组的一部分. 简单地说, 如果你引发一个 ValueError , 那么
最好提供和解释器引发 ValueError 时一致的参数信息, 如此类推. 这样可以在保证代码一致性,
同时也能避免其他应用程序在使用你的模块时发生错误.
else 子句
在try 范围中没有异常被检测到时,执行else 子句.
在else 范围中的任何代码运行前,try 范围中的所有代码必须完全成功(也就是,结束前没有引发异常).
import 3rd_party_module
log = open(‘logfile.txt‘, ‘w‘)
try:
3rd_party_module.function()
except:
log.write("*** caught exception in module\n")
else:
log.write("*** no exceptions caught\n")
log.close()
finally 子句
finally 子句是无论异常是否发生,是否捕捉都会执行的一段代码.
可以将finally 仅仅配合try 一起使用,也可以和try-except(else 也是可选的)一起使用.
try-finally 语句
try:
try_suite
finally:
finally_suite #无论如何都执行
当在try 范围中产生一个异常时,(这里)会立即跳转到finally 语句段.当finally 中的所有代码都执行完毕后,会继续向上一层引发异常.
如果finally 中的代码引发了另一个异常或由于return,break,continue 语 法而终止,原来的异常将丢失而且无法重新引发.
try-except-else-finally:厨房一锅端
所有不同的可以处理异常的语法样式:
try:
try_suite
except Exception1:
suite_for_Exception1
except (Exception2, Exception3, Exception4):
suite_for_Exceptions_2_3_and_4
except Exception5, Argument5:
suite_for_Exception5_plus_argument
except (Exception6, Exception7), Argument67:
suite_for_Exceptions6_and_7_plus_argument
except:
suite_for_all_other_exceptions
else:
no_exceptions_detected_suite
finally:
always_execute_suite
10.4 上下文管理
with 语句 (C# using(IDisposalbe)
try-except 和try-finally 的一种特定的配合用法是保证共享的资源的唯一分配,并在任务结束的时候释放它.
比如文件(数据,日志,数据库等等),线程资源,简单同步,数据库连接,等等. with 语句的目标就是应用在这种场景.
with context_expr [as var]:
with_suite
with 语句仅能工作于支持上下文管理协议(context managementprotocol)的对象.
支持 上下文管理协议 的对象简短列表:
?? file
?? decimal.Context
?? thread.LockType
?? threading.Lock
?? threading.RLock
?? threading.Condition
?? threading.Semaphore
?? threading.BoundedSemaphore
*上下文管理协议
上下文表达式(context_expr),上下文管理器
__context__()方法
上下文对象,with 语句块
__enter__()方法
__exit__()方法
contextlib 模块
上下文管理器主要作用于共享资源
10.5 *字符串作为异常
早在Python 1.5 前,标准的异常是基于字符串实现的.
到1.5 为止,所有的标准异常都是类了.
程序员还是可以用字符串作为自己的异常的,但是我们建议从现在起使用异常类.
你可能用到仍然使用着字符串异常的外部或第三方的模块.
10.6 触发异常
Python 提供了一种机制让程序员明确的触发异 常:这就是raise 语句.
raise 语句
语法与惯用法
rasie 一般的用法是:
raise [SomeException [, args [, traceback]]]
第一个参数,SomeExcpetion,是触发异常的名字.如果有,它必须是一个字符串,类或实例
第二个符号为可选的args(比如参数,值),来传给异常.
这可以是一个单独的对象也可以是一个对象的元组.
当异常发生时,异常的参数总是作为一个元组传入.
如果args 原本就是元组,那么就将其传给异常去处理;
如果args 是一个单独的对象,就生成只有一个元素的元组(就是单元素元组).
大多数情况下,单一的字符串用来指示错误的原因.
如果传的是元组,通常的组成是一个错误字符串,一个错误编号,可能还有一个错误的地址,比如文件,等等.
最后一项参数,traceback,同样是可选的(实际上很少用它),
如果有的话,则是当异常触发时新生成的一个用于异常-正常化(exception—normally)的追踪(traceback)对象.
当你想重新引发异常时,第三个参数很有用(可以用来区分先前和当前的位置).
如果没有这个参数,就填写None.
最常见的用法为SomeException 是一个类.
不需要其他的参数,但如果有的话,可以是一个单一对象参数,一个参数的元组,或一个异常类的实例.
如果参数是一个实例,可以由给出的类及其派生类实例化(已存在异常类的子集).若参数为实例,则不能有更多的其他参数.
更多的特殊/少见的惯用法
10.7 断言
断言是一句必须等价于布尔真的判定;此外,发生异常也意味着表达式为假.
断言可以简简单单的想象为raise-if 语句(更准确的说是raise-if-not 语句).测试一个表达式,如果返回值是假,触发异常.
断言通过assert 语句实现.
断言语句
断言语句等价于这样的Python 表达式,如果断言成功不采取任何措施(类似语句),否则触发AssertionError(断言错误)的异常.
assert 的语法如下:
assert expression[, arguments]
AssertionError 异常和其他的异常一样可以用try-except 语句块捕捉,但是如果没有捕捉,它将终止程序运行而且提供一个traceback.
>>> assert True
>>> assert False,‘Hello‘
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError: Hello
>>>
想象断言语句在Python 中的函数实现.可以像下面这样:
def assert(expr, args=None):
if __debug__ and not expr:
raise AssertionError, args
内建的变量__debug__在通常情况下为True,如果开启优化后为False(命令行选项-O).
10.8 标准异常
所有的异常都是内建的. 所以它们在脚本启动 前或在互交命令行提示符出现时已经是可用的了.
所有的标准/内建异常都是从根异常派生的.
目前,有3 个直接从BaseException 派生的异常子类:SystemExit,KeyboardInterrupt 和Exception.
其他的所有的内建异常都是Exception 的子类.
SystemExit 和KeyboardInterrupt 从Exception 的继承中移到BaseException 的继 承下, 这样可以允许如except Exception 的语句捕获所有非控制程序退出的异常.
从Python2.5 开始,不再支持构建基于字符串的异常并且被正式的弃用,也就是说你不能再触发一个字符串异常了.
10.9 *创建异常

1 #!/usr/bin/env python
2
3 import os, socket, errno, types, tempfile
4
5 class NetworkError(IOError):
6 pass
7
8 class FileError(IOError):
9 pass
10
11 def updArgs(args, newarg=None):
12 if isinstance(args, IOError):
13 myargs = []
14 myargs.extend([arg for arg in args])
15 else:
16 myargs = list(args)
17
18 if newarg:
19 myargs.append(newarg)
20 return tuple(myargs)
21
22 def fileArgs(file, mode, args):
23 if args[0] == errno.EACCES and 24 ‘access‘ in dir(os):
25 perms = ‘‘
26 permd = { ‘r‘: os.R_OK, ‘w‘: os.W_OK, ‘x‘: os.X_OK}
27 pkeys = permd.keys()
28 pkeys.sort()
29 pkeys.reverse()
30
31 for eachPerm in ‘rwx‘:
32 if os.access(file, permd[eachPerm]):
33 perms += eachPerm
34 else:
35 perms += ‘-‘
36
37 if isinstance(args, IOError):
38 myargs = []
39 myargs.extend([arg for arg in args])
40 else:
41 myargs = list(args)
42
43 myargs[1] = "‘%s‘ %s (perms: ‘%s‘)" % 44 (mode, myargs[1], perms)
45
46 myargs.append(args.filename)
47
48 else:
49 myargs = args
50
51 return tuple(myargs)
52
53 def myconnect(sock, host, port):
54 try:
55 sock.connect((host, port))
56
57 except socket.error, args:
58 myargs = updArgs(args) # conv inst2tuple
59 if len(myargs) == 1: # no #s on some errs
60 myargs = (errno.ENXIO, myargs[0])
61
62 raise NetworkError, 63 updArgs(myargs, host + ‘:‘ + str(port))
64
65 def myopen(file, mode=‘r‘):
66 try:
67 fo = open(file, mode)
68 except IOError, args:
69 raise FileError, fileArgs(file, mode, args)
70
71 return fo
72
73 def testfile():
74
75 file = mktemp()
76 f = open(file, ‘w‘)
77 f.close()
78
79 for eachTest in ((0, ‘r‘), (0100, ‘r‘),
80 (0400, ‘w‘), (0500, ‘w‘)):
81 try:
82 os.chmod(file, eachTest[0])
83 f = myopen(file, eachTest[1])
84
85 except FileError, args:
86 print "%s: %s" % 87 (args.__class__.__name__, args)
88 else:
89 print file, "opened ok... perm ignored"
90 f.close()
91
92 os.chmod(file, 0777)# enable all perms
93 os.unlink(file)
94
95 def testnet():
96 s = socket.socket(socket.AF_INET,
97 socket.SOCK_STREAM)
98
99 for eachHost in (‘deli‘, ‘www‘):
100 try:
101 myconnect(s, ‘deli‘, 8080)
102 except NetworkError, args:
103 print "%s: %s" % 104 (args.__class__.__name__, args)
105
106 if __name__ == ‘__main__‘:
107 testfile()
108 testnet()
myexec.py
10.10 为什么用异常(现在)?
毫无疑问,错误的存在会伴随着软件的存在.区别在于当今快节奏的计算世界, 我们的执行环境已经改变, 所以我们需要改变错误处理, 以准确反映我们软件的开发环境. 就现今应用来说, 普遍的是自洽(self-contained)的图形用户界面(GUIs)或是客户机/服务器体系, 例如Web.在应用层处理错误的能力近来变得更为重要, 用户已不再是应用程序的的唯一的直接运行者.随着互联网和网上电子商业应用越来越普及, web 服务器将成为应用软件的主要客户. 这意味着应用程序再也不能只是直接的失败或崩溃, 因为如果这样, 系统错误导致浏览器的错误, 这反过来又会让用户沮丧. 失去眼球意味着失去广告收入和和潜在的大量无可挽回的生意.如果错误的确发生了, 它们一般都归因于用户输入的数据无效. 运行环境必须足够强健,来处理应用级别的错误,并提供用户级别的错误信息.就服务器而言,这必须转化为一个"非错误" . 因为应用必须要成功的完成, 即使所做的不过是返回一个错误的信息, 向用户是提供一个有效的超文本标记语言(HTML)的网页指明错误.
如果你不清楚我在说什么, 那个一个简单的网页浏览器窗口,用大而黑的字体写到"内部服务器错误"是否更耳熟?用一个弹出式窗口宣告"文件中没有数据"的致命错误如何?作为一个用户, 这些词语对你有意义吗?没有, 当然没有(除非你是一个互联网软件工程师), 至于对普通用户来说,这些是无休止的混乱和挫折感的来源. 这些错误导致在执行的程序时的失败. 应用不论是返回无效的超文本传输协议( http)数据还是致命地终止, 都会导致Web 服务器举手投降, 说: "我放弃" !这种类型的执行错误不应该被允许, 无论情况如何. 随着系统变得更加复杂, 又牵涉到更多的新手用户, 要采取额外的措施, 确保用户平滑的学到应用经验. 即使面对一个错误, 应用应该成功的中止, 不至于灾难性的影响其执行环境. Python 异常处理促使成熟和正确的编程.
10.11 到底为什么要异常?
如果上文的动机不够充分, 试想Python 编程没有程序级的异常处理. 第一件事需要担心的是客户端程序员在自己的代码中遗忘控制. 举例来说, 如果你创造了一个交互的应用程序分配并使用了大量的资源, 如果一个用户击中Ctrl+C 或其他键盘中断, 应用程序将不会有机会执行清理工作, 可能导致数据丢失或数据损坏. 此外, 也没有机制来给出可选的行为, 诸如提示用户, 以确认他们真的是想退出或是他们意外的按下了Ctrl 键.另一个缺点就是函数必须重写来为错误的情形返回一个"特殊"的值, 如:None. 程序员要负责检查每一个函数调用的返回值. 这可能是个麻烦, 因为你可能不得不检查返回值, 这和没有发生错误时你期待结果也许不是同一类型的对象. 什么,你的函数要把None 作为一个有效的数值返回?那么, 你将不得不拿出另一个返回值, 也许是负数.我们也许并不需要提醒你, 在Python 的环境下负数下可能是有效的, 比如作为一个序列的索引. 作为一个写应用程序接口( API )的程序员, 你不得不为每个一个用户输入可能遇到的返回错误写文档. 同时, 我们难以(而且乏味)在多层次的代码中以传播错误(和原因).
没有一个简单的传播方法像异常一样做到这一点. 因为错误的数据需要在调用层次中向上转发,但在前进的道路上可能被曲解. 一个不相干的错误可能会被宣布为起因,而实际上它与原始问题完全无关.在一层一层的传递中,我们失去了对原始错误封装和保管的能力,更不用说完全地失去我们原本关心的数据的踪影!异常不仅简化代码, 而且简化整个错误管理体系 --- 它不该在应用开发中如此重要角色;而有了Python 的异常处理能力, 也的确没有必要了.
10.12 异常和sys 模块
另一种获取异常信息的途径是通过sys 模块中exc_info()函数.
>>> try:
... float(‘abc123‘)
... except:
... import sys
... exc_tuple = sys.exc_info()
...
>>> print(exc_tuple)
(<class ‘ValueError‘>, ValueError("could not convert string to float: ‘abc123‘",), <traceback object at 0x000000000296EE88>)
>>> for eachItem in exc_tuple:
... print(eachItem)
...
<class ‘ValueError‘>
could not convert string to float: ‘abc123‘
<traceback object at 0x000000000296EE88>
>>>
sys.exc_info()元组:
?? exc_type: 异常类
?? exc_value: 异常类的实例
?? exc_traceback: 追踪(traceback)对象
前两项:实际的异常类, 和这个异常类的实例 .
第三项, 是一个新增的追踪(traceback)对象.
这一对象提供了的发生异常的上下文.
它包含诸如代码的执行帧,异常发生时的行号等信息.
在旧版本中的Python 中, 这三个值分别存在于sys 模块, 为sys.exc_type , sys.exc_value ,sys.exc_traceback .
不幸的是, 这三者是全局变量而不是线程安全的. 我们建议亡羊补牢, 用sys.exc_info()来代替.
在未来版本Python 中,所有这三个变量都将被逐步停用,并最终移除.
10.13 相关模块
异常相关的标准库
模块 描述
exceptions 内建异常(永远不用导入这个模块)
contextlib 为使用with 语句的上下文对象工具
sys 包含各种异常相关的对象和函数(见sys.ex*)
10 错误和异常 - 《Python 核心编程》
标签:
原文地址:http://www.cnblogs.com/BugQiang/p/4735025.html