2015/9/10 Python基础(11):错误和异常

程序在执行的过程中会产生异常,出现错误在以前的一个时期是致命的,后来随着程序的发展,使得一些错误的处理方式是柔和的,发生错误会产生一些错误的诊断信息和一些模糊的提示。帮助我们来处理异常。
今天将学习Python的异常。

程序会有语法或者逻辑上的错误,语法的错误会导致解释器或编译器无法工作,那么程序并U币能执行。
而语法正确的情况下,只有逻辑的问题。逻辑的问题分为两种情况,一个是由于不完整或者不合法的输入所致。另一个是逻辑无法生成,计算或者执行,这些错误也叫域错误和范围错误。
Python检测到错误时,就出现了异常。

异常是因为程序出现了错误而在正常控制论外采取的行为。这个行为有两个阶段:
1、异常引发的错误阶段
2、检测(和采取可能的措施)阶段
第一个阶段是检测到错误并意识到异常条件,然后解释器会引发一个异常,打断当前流。当然Python允许程序员自己引发异常。
第二阶段是处理异常,有忽略错误(记录错误但不采取措施,采取补救措施后终止程序),或是减轻问题的影响后设法继续执行程序。
类似Python这样支持引发和处理异常的语言,可以让开发人员在错误发生时更直接地控制它们。这样会使程序的健壮性大大提高。

Python中有的异常
每次当程序崩溃或者因错误终止时,会看到"trackback/跟踪返回"消息,有解释器提供的信息,包括错误的名称,原因,以及发生错误的行号。
有以下的异常例子:

NameError: 尝试访问一个未申明的变量
>>> foo
Traceback (innermost last): File "<stdin>", line 1, in ?
NameError: name 'foo' is not defined

这是访问了没有初始化的变量。访问变量将由解释器进行搜索,如果请求的名字没有在任何名称空间里找到,就会生成一个NameError异常。

ZeroDivisionError: 除数为零
>>> 1/0
Traceback (innermost last): File "<stdin>", line 1, in ?
ZeroDivisionError: integer division or modulo by zero

任何数值被零除都会引发ZeroDivisionError异常

SyntaxError: Python 解释器语法错误
>>> for
File "<string>", line 1
for
^
SyntaxError: invalid syntax

SyntaxError异常是唯一一个不再运行时发生的异常。在改正它之前程序无法执行。

IndexError:请求的索引超出序列范围
>>> aList = []
>>> aList[0]
Traceback (innermost last): File "<stdin>", line 1, in ?
IndexError: list index out of range

IndexError 在你尝试使用一个超出范围的值索引序列时引发.

KeyError:请求一个不存在的字典关键字
>>> aDict = {'host': 'earth', 'port': 80}
>>> print aDict['server'] Traceback (innermost last):
File "<stdin>", line 1, in ? KeyError: server

使用错误的键请求字典就会引发一个KeyError异常。

IOError: 输入/输出错误
>>> f = open("blah") Traceback (innermost last):
File "<stdin>", line 1, in ?
IOError: [Errno 2] No such file or directory: 'blah'

类似尝试打开一个不存在的磁盘文件一类的操作会引发一个操作系统输入/输出(I/O)错误. 任何类型的 I/O 错误都会引发 IOError 异常.

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

在 myInst.bar 储存了一个值, 也就是实例 myInst 的 bar 属性. 属性被定义后, 我们可以使用熟悉的点/属性操作符访问它, 但如果是没有定义属性, 例如我们访问foo 属性, 将导致一个 AttributeError 异常.

检测和处理异常
Python的异常检测处理有专门的语法
异常通过try语句来检测,任何在try语句块里的代码都会被检测,检查有无异常发生。
try语句主要有两种主要形式:try-except 和 try-finally.这两种语句互斥,只能使用其中一种。一个try语句对应一个或多个 except 子句,但只能对应一个 finally 子句, 或是一个 try-except-finally 复合语句。
try-except语句检测和处理异常,可以添加一个可选的 else 子句处理没有探测到异常时执行的代码。而 try-finally 只允许检测异常并做一些必要的清楚工作,没有任何异常处理措施。复合语句可以做这两者做到。

try-except语句语法如下:

try:
  try_suite # 监控这里的异常
except Exception[, reason]:
  except_suite # 异常处理代码

一下是一个例子:

>>> try:
  f = open('try', 'r')
except IOError, e:
  print 'could not open file:', e

could not open file: [Errno 2] No such file or directory: 'try'

在这个例子里,我们只捕捉 IOError 异常,任何其他异常都不会被处理器捕获。

核心笔记:忽略代码,继续执行,向上移交
try 语句块中异常发生点后的剩余语句永远不会到达,一旦一个异常被引发,就必须决定控制流下一步到达的位置。剩余代码将被忽略,解释器将搜索处理器,一旦找到,就开始执行处理器中的代码。
如果没有找到合适的处理器,那么异常就向上移交给调用者去处理,这意味着堆栈框架立即回到之前的那个。如果在上层调用者也没有找到对应处理器,该异常会继续被向上移交,直到找到合适的处理器,如果到达最顶层仍然没有找到对应处理器,那么就认为这个异常是未处理的,Python解释器会显示出跟踪返回消息,然后退出。

安全地调用内建函数
在下例中,我们将看到当给float()错误的值后会得到的错误,然后用try-except封装一个安全的函数:

>>> float('abc')

Traceback (most recent call last):
File "<pyshell#5>", line 1, in <module>
float('abc')
ValueError: could not convert string to float: abc
>>> float(['this is', 1, 'list'])

Traceback (most recent call last):
File "<pyshell#6>", line 1, in <module>
float(['this is', 1, 'list'])
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 = 'object type cannot be converted to float'
  return retval

>>> safe_float('xyz')
'could not convert non-number to float'
>>> safe_float(['this is', 1, 'list'])
'object type cannot be converted to float'

这里使用了多个except子句的处理,语法如下:
except Exception1[, reason1]:
  suite_for_exception_Exception1
except Exception2[, reason2]:
  suite_for_exception_Exception2
 ...

 

 

还可以在一个except子句里处理多个异常,这种处理是要求异常被放在一个元组里:

except (Exception1, Exception2)[, reason]:
  suite_for_Exception1_and_Exception2

用这种方法改变上面的函数,可以写成这样:

def safe_float(obj):
  try:
    retval = float(obj)
  except (ValueError, TypeError):
    retval = 'argument must be a number or numeric string'
  return retval

except语句可以处理任意多个异常,前提是放在一个元组,如下:

except (Exc1,...ExcN)[, reason]:
  suite_for_exceptions_Exc1_to_ExcN

而但我们想捕获所有的异常呢?
可以如下来写:

try:
  ...
except Exception, e:
  ...# 发生了异常,异常种类存储在e里

另一个不太推荐的方法是用裸 except 子句:

try:
  ...
except:
  ...

下面这种裸 except 子句的方法虽然捕获大多异常,但你并不会知道导致异常的主要原因。所以没办法学会避免,故而不推荐这种方法。也许以后会取消这种方法的支持
关于捕获所有异常,有些异常不是由错误条件引起。他们是 SystemExit 和 KeyboardInterrupt.
SystemExit 是由于当前 Python 应用程序需要退出, KeyboardInterupt 代表用户按下了 CTRL—C(^C),想要关闭 Python。在真正需要时,这些异常会被异常处理捕获。
在Python2.5以后,异常发生了迁移,启用了一个BaseException作为所有异常的母亲,KeyboardInterrupt 和 SystemExit 从 Exception 里移出,和 Exception 平级:

-BaseException
  |- KeyboardException
  |- SystemExit
  |- Exception
  |- (all other current built-in exceptions)

 

核心风格: 不要处理并忽略所有的错误
Python提供 try—except 语句是为了更好地跟踪潜在的错误并在代码里准备好处理异常的逻辑。它的母的是为了减少程序出错的次数并在出错后仍能保证程序正常运行。
如果我们一直用一个通用的except过滤了任何致命的错误,忽略它们。比如说使用裸 except 语句。
我们知道错误无法避免,try—except的作用是提供一个可以提示错误或者处理错误的机制,而不是错误过滤器,过滤任何致命的错误是没有意义的。

异常参数
异常也可以有参数,异常引发后它会被传递给异常处理器。当异常被引发后参数是作为附加帮助信息传递给异常处理器的。虽然异常原因是可选的,但标准内建异常提供至少一个参数,指示异常原因的一个字符串。
异常的参数需要一个变量来保存,放在 except 后接在要处理的异常后面。也就是上面那个例子里我使用的e变量
单个异常的语句里:

except Exception[, reason]:
  suite_for_Exception_with_Argument

except (Exception1, Exception2, ..., ExceptionN)[, reason]:
  suite_for_Exception1_to_ExceptionN_with_Argument

这个reason将会是一个包含来自导致异常的代码的诊断信息的类实例。异常参数自身会组成一个元组,并存储为类实例的属性。上面这样的用法,reason将会是Exception类的实例。
大部分的内建异常都是从StandardError派生的异常,这个元组只包含一个指示错误原因的字符串,一般来说,名字就是一个满意的线索了。
而某些第三方或是其他外部库并不遵循这个标准协议。

核心风格: 遵循异常参数规范
在自己的代码里引发内建(built-in)的异常时,尽量遵循规范,用和已有Python代码一致信息作为传递给异常的参数元组的一部分。这样可以保证代码一致性,同时也能避免其他应用程序在使用你的模块时发生错误。

同时try-except语句还可以加上else语句
和正常的执行没有什么不同,也就是在try范围中没有异常被检测到时,执行else子句。

finally子句
finally子句是无论异常是否发生,是否捕捉都会的执行一段代码。

try—finally
语法如下:

try:
    try_suite
finally:
    finally_suite

当 try 范围中产生一个异常时,会立即跳转到finally语句段,当finally中所有代码执行完毕后,会继续向上一层引发异常。

同样的,我们还有try-except-else-finally语句,可以使用整个过程

 

上下文管理
with语句
with也是用于简化代码的,隐藏了底层的应用。try-except 和 try-finally的一种特定的配合用法是保证共享资源的唯一分配,并在任务结束时释放它,比如文件,线程资源,简单同步,数据库连接等等,with语句的目标就是应用在这种场景。
with语句的目的在于从流程图中把 try,except 和finally 关键字和资源分配释放相关代码统统去掉,而不是像 try-except-finally那样仅仅简化代码使之易用。with语法基本用法如下:
with context_expr [as var]:
with_suite
这看起来很简单,但是其背后还有一些工作要做。这并不如看上去的那么容易,因为不能对 Python 的任意符号使用 with 语句。它仅能工作于支持上下文管理协议的对象。
支持该协议的对象暂时有如下:
file
decimal.Context
thread.LockType
threading.Lock
threading.RLock
threading.Condition
threading.Semaphore
threading.BounderSemaphore
以file举例

with open(r'\file.txt','r') as f:
for eachLine in f:
  # ...do stuff with eachLine or f...

这段代码会完成准备工作,比如试图打开一个文件,如果一切正常,把文件对象赋值给 f.然后用迭代器遍历文件中的每一行,当完成时,关闭文件。无论在这一段代码的开始,中间,还是结束是发生异常,会执行清理的代码,此外文件仍会被自动的关闭。

触发异常
除了解释器引发的异常,程序员编写API时也希望在遇到错误的输入时触发异常,为此,Python提供了一种机制让程序员明确的触发异常,就是raise语句。
raise的一般用法是:

raise [SomeException [, arges [, trackback]]]

第一个参数,SomeException,是触发异常的名字。如果有,它必须是一个字符串,类或实例.如果有其他参数(arg 或 traceback)就必须提供SomeException.
第二个符号为可选的 args ,来传给异常。这可以是一个单独的对象也可以是一个对象的元组。如果args原本就是元组,那么就将其传给异常区处理;如果args是一个单独的对象,就生成只有一个元素的元组。大多数情况下,单一的字符串用来指示错误的原因。如果传的是元组,通常的组成是一个错误字符串,一个错误编号,可能还有一个错误的地址,比如文件。
最后一个参数,traceback,也是可选的。如果有的话,是新生成的一个用于异常-正常话的追踪对象。当你想重新引发异常时,第三个参数很有用。

断言
断言是一句必须等价于布尔真的判定;此外发生发生异常也意味着表达式为假。
断言语句语法:

assert expression[, arguments]

断言成功不采取任何措施,否则处罚AssertionError异常。


为什么用异常?
毫无疑问,只要软件存在,错误就会一直存爱。区别在于当今快节奏的计算世界,我们的执行环境已经改变,所以需要改变错误处理,以准确反映软件的开发环境。
在互联网和网上电子商业应用越来越普及的过程中,web服务器将是应用软件的主要客户,意味着应用程序再也不能只是直接失败或崩溃,否则会导致浏览器的错误。
而一些用户输入的数据无效的错误,必须转化成一个非错误,所以运行环境必须够强健。我们不能仅仅返回这是一个错误的信息。而应该返回足够多的信息,确保用户平滑地学到使用经验。
即使面对一个错误,应用应该成功的中止,不至于灾难性地影响执行环境。

posted @ 2015-09-10 22:56  #SRL  阅读(744)  评论(0编辑  收藏  举报