#encoding=utf-8 ######### 错误和异常 ############## ## ************ Part 1: 异常 ***************** #####错误 #从软件方面来说, 错误是语法或是逻辑上的.语法错误指示软件的结构上有错误, 导致不能被解释器解释或编译器无法编译. #这些错误必须在程序执行前纠正. #当程序的语法正确后, 剩下的就是逻辑错误了. 逻辑错误可能是由于不完整或是不合法的输入所致; #在其他情况下, 还可能是逻辑无法生成, 计算, 或是输出结果需要的过程无法执行. 这些错误通常分别被称为域错误和范围错误. #当 Python 检测到一个错误时, 解释器就会指出当前流已经无法继续执行下去. 这时候就出现了异常. #######异常 #对异常的最好描述是: 它是因为程序出现了错误而在正常控制流以外采取的行为. 这个行为又 #分为两个阶段: 首先是引起异常发生的错误, 然后是检测(和采取可能的措施)阶段. #第一个阶段是在发生了一个异常条件(有时候也叫做例外的条件)后发生的. 只要检测到错误 #并且意识到异常条件, 解释器会引发一个异常. 引发也可以叫做触发, 引发或者生成. 解释器通 #过它通知当前控制流有错误发生. Python 也允许程序员自己引发异常. 无论是 Python 解释器还是 #程序员引发的, 异常就是错误发生的信号. 当前流将被打断, 用来处理这个错误并采取相应的操作. #这就是第二阶段. #对异常的处理发生在第二阶段. 异常引发后, 可以调用很多不同的操作. 可以是忽略错误(记 #录错误但不采取任何措施, 采取补救措施后终止程序), 或是减轻问题的影响后设法继续执行程序. #所有的这些操作都代表一种继续, 或是控制的分支. 关键是程序员在错误发生时可以指示程序如何执行. ## ************ Part 2: Python 中的异常 ***************** #####NameError 表示我们访问了一个没有初始化的变量. 在 Python 解释器的符号表没有找到那个另人讨厌的变量. #任何可访问的变量必须在名称空间里列出. 访问变量需要由解释器进行搜索, #如果请求的名字没有在任何名称空间里找到, 那么将会生成一个 NameError 异常. #print(dave) #-->NameError: name 'dave' is not defined ######ZeroDivisionError: 除数为零 #print(1/0) #-->ZeroDivisionError: division by zero ######SyntaxError: Python 解释器语法错误 #print(for) #-->SyntaxError: invalid syntax #SyntaxError 异常是唯一不是在运行时发生的异常. 它代表 Python 代码中有一个不正确的结 #构, 在它改正之前程序无法执行. 这些错误一般都是在编译时发生, Python 解释器无法把你的脚本 #转化为 Python 字节代码. 当然这也可能是你导入一个有缺陷的模块的时候. ######## IndexError:请求的索引超出序列范围 #a=[] #print(a[0]) #-->IndexError: list index out of range ####### KeyError:请求一个不存在的字典关键字 #aDict = {'host': 'earth', 'port': 80} #print(aDict['server']) #-->KeyError: 'server' #映射对象, 例如字典, 是依靠关键字(keys)访问数据值的. 如果使用错误的或是不存在的键请求字典就会引发一个 KeyError 异常. #### IOError: 输入/输出错误 #f=open('anqing.txt') #print(f) #-->IOError: [Errno 2] No such file or directory: 'anqing.txt' #类似尝试打开一个不存在的磁盘文件一类的操作会引发一个操作系统输入/输出(I/O)错误. 任 #何类型的 I/O 错误都会引发 IOError 异常. #### AttributeError: 尝试访问未知的对象属性 #class myClass(object): # pass # #myInst = myClass() #myInst.bar = 'spam' #print(myInst.bar) #print(myInst.dave) #-->AttributeError: 'myClass' object has no attribute 'dave' ## ************ Part 3: 检测和处理异常 ***************** #异常可以通过 try 语句来检测. 任何在 try 语句块里的代码都会被监测, 检查有无异常发生. #try 语句有两种主要形式: try-except 和 try-finally . 这两个语句是互斥的, 也就是说你 #只能使用其中的一种. 一个 try 语句可以对应一个或多个 except 子句, 但只能对应一个 #finally 子句, 或是一个 try-except-finally 复合语句. # 可以使用 try-except 语句检测和处理异常. 你也可以添加一个可选的 else 子句处理没 #有探测到异常的时执行的代码. 而 try-finally 只允许检测异常并做一些必要的清除工作(无论 #发生错误与否), 没有任何异常处理设施. 正如你想像的,复合语句两者都可以做到. ########### try-except 语句 # try-except 语句(以及其更复杂的形式)定义了进行异常监控的一段代码, 并且提供了处理异 #常的机制. # 最常见的 try-except 语句语法如下所示. 它由 try 块和 except 块(try_suite 和 #except_suite )组成, 也可以有一个可选的错误原因. # try: # try_suite # watch for exceptions here 监控这里的异常 # except Exception[, reason]: # except_suite # exception-handling code 异常处理代码 #这是Python 2.x 的语法, 在3.x里,使用as 来表示异常的原因,示例如下: #import sys #try: # f = open('Dave.txt') # s = f.readline() # print(s) #except IOError as err: # print("I/O 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 语句继续执行. 而当 except 语句所指定的异常发生后, 我们保存了错误的 #原因, 控制流立即跳转到对应的处理器( try 子句的剩余语句将被忽略), 本例中我们显示出一个包 #含错误原因的错误信息. #核心笔记: 忽略代码, 继续执行, 和向上移交 #try 语句块中异常发生点后的剩余语句永远不会到达(所以也永远不会执行). 一旦一个异常被 #引发, 就必须决定控制流下一步到达的位置. 剩余代码将被忽略, 解释器将搜索处理器, 一旦找到, #就开始执行处理器中的代码. #如果没有找到合适的处理器, 那么异常就向上移交给调用者去处理, 这意味着堆栈框架立即回 #到之前的那个. 如果在上层调用者也没找到对应处理器, 该异常会继续被向上移交, 直到找到合适 #处理器. 如果到达最顶层仍然没有找到对应处理器, 那么就认为这个异常是未处理的, Python 解释 #器会显示出跟踪返回消息, 然后退出. ########### 封装内建函数 #def safe_float(obj): # try: # retval = float(obj) # except ValueError as err: # retval = 'could not convert non-number to float'+' ||||| '+format(err) # return retval #在这个函数里,我们将一个对象转换称fload #print(safe_float('dmm')) #-->could not convert non-number to float ||||| could not convert string to float: 'dmm' #这里的format(err) 就是我们的错误内容 ##### 带有多个 except 的 try 语句 #在Python 2.x 中语法如下: #except Exception1[, reason1]: # suite_for_exception_Exception1 #except Exception2[, reason2]: # suite_for_exception_Exception2 #Python 3.x 示例: #def safe_float(obj): # try: # retval = float(obj) # except ValueError as err: # retval = format(err) # except TypeError as err1: # retval = format(err1) # return retval #print(safe_float('dmm')) #-->could not convert string to float: 'dmm' #print(safe_float(('1','2'))) #-->float() argument must be a string or a number ########## 处理多个异常的 except 语句 #Python 2.x: #except (Exception1, Exception2)[, reason]: # suite_for_Exception1_and_Exception2 # #Python 3.x: #except (Exception1, Exception2)[as reason]: # suite_for_Exception1_and_Exception2 #Python 3.x 示例: #def safe_float(obj): # try: # retval = float(obj) # except (ValueError, TypeError) as err: # #retval = format(err) # retval = str(err) # return retval # #print(safe_float('dmm')) #print(safe_float(('1','2'))) #--> #could not convert string to float: 'dmm' #float() argument must be a string or a number #### 捕获所有异常 #在Python 2.5以后,Python有了一个基本类:BaseException。 如果要捕获所有异常,就需要捕获该对象。 #在联机文档:Built-in Exceptions 有详细的说明。 #在Python 2.5以前: #try: # : #except Exception, e: # # handle real errors #注意这里不包含KeyboardInterrupt 和 SystemExit异常。 #Python 2.5 以后所有异常捕获: #try: # : #except BaseException, e: # #python 3.x #try: # : #except BaseException as e: #避免把大片的代码装入 try-except 中然后使用 pass 忽略掉错误. 你可以捕获特定 #的异常并忽略它们, 或是捕获所有异常并采取特定的动作. 不要捕获所有异常,然后忽略掉它们 ########## 异常参数, #异常也可以有参数, 异常引发后它会被传递给异常处理器. 当异常被引发后参数是作为附加帮 #助信息传递给异常处理器的. 虽然异常原因是可选的, 但标准内建异常提供至少一个参数, 指示异 #常原因的一个字符串. #异常的参数可以在处理器里忽略, 但 Python 提供了保存这个值的语法. 我们已经在上边接触 #到相关内容: 要想访问提供的异常原因, 你必须保留一个变量来保存这个参数. 把这个参数放在 #except 语句后, 接在要处理的异常后面. except 语句的这个语法可以被扩展为: #Python 2.x ## single exception #except Exception[, reason]: #suite_for_Exception_with_Argument # #Python 3.x ## single exception #except Exception[as reason]: #suite_for_Exception_with_Argument #reason 将会是一个包含来自导致异常的代码的诊断信息的类实例. 异常参数自身会组成一个元组, #并存储为类实例(异常类的实例)的属性. 上边的第一种用法中, reason 将会是一个Exception 类的实例. #对于大多内建异常, 也就是从 StandardError 派生的异常, 这个元组只包含一个指示错误原 #因的字符串. 一般说来, 异常的名字已经是一个满意的线索了, 但这个错误字符串会提供更多的信 #息. 操作系统或其他环境类型的错误, 例如 IOError , 元组中会把操作系统的错误编号放在错误字 #符串前. #无论 reason 只包含一个字符串或是由错误编号和字符串组成的元组, 调用 str(reason) 总 #会返回一个良好可读的错误原因. 不要忘记 reason 是一个类实例 - 这样做你其实是调用类的特 #殊方法 __str__() . ######## else 子句 #在try 范围中没有异常被检测到时,执行else 子句. #import sys #for arg in sys.argv[1:]: # try: # f = open(arg, 'r') # except IOError: # print('cannot open', arg) # else: # print(arg, 'has', len(f.readlines()), 'lines') # f.close() ######### finally 子句 #finally 子句是无论异常是否发生,是否捕捉都会执行的一段代码.你可以将finally 仅仅配合 #try 一起使用,也可以和try-except(else 也是可选的)一起使用. #下面是try-except-else-finally 语法的示例: #try: # A #except MyException: B #else: C #finally: D #当然,无论如何,你都可以有不止一个的except 子句,但最少有一个except 语句,而else 和 #finally 都是可选的.A,B,C 和D 是程序(代码块).程序会按预期的顺序执行 ######### try-finally 语句 #另一种使用finally 的方式是finally 单独和try 连用.这个try-finally 语句和try-except #区别在于它不是用来捕捉异常的.作为替代,它常常用来维持一致的行为而无论异常是否发生.我们 #得知无论try 中是否有异常触发,finally 代码段都会被执行 #try: # try_suite # finally: #finally_suite #无论如何都执行 #当在try 范围中产生一个异常时,(这里)会立即跳转到finally 语句段.当finally 中的所有代码都执行完毕后,会继续向上一层引发异常. ####### 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 ## ************ Part 4: 上下文管理 ***************** ##### with 语句 #with 语句一个隐藏低层次的抽象的例子,它在Python 2.6 中正式启用。 #类似try-except-finally , with 语句也是用来简化代码的。 #try-except 和try-finally 的一种特定的配合用法是保证共享的资源的 #唯一分配,并在任务结束的时候释放它.比如文件(数据,日志,数据库等等),线程资源,简单同步,数 #据库连接,等等. with 语句的目标就是应用在这种场景. #然而,with 语句的目的在于从流程图中把 try,except 和finally 关键字和资源分配释放相关 #代码统统去掉, 而不是像 try-except-finally 那样仅仅简化代码使之易用. with 语法的基本用法 #看上去如下: #with context_expr [as var]: # with_suite #看起来如此简单,但是其背后还有一些工作要做.这并不如看上去的那么容易,因为你不能对 #Python 的任意符号使用with 语句.它仅能工作于支持上下文管理协议(context management #protocol)的对象.这显然意味着只有内建了"上下文管理"的对象可以和with 一起工作. #目前已经有了一些支持该协议的对象.下面是第一批成员的简短列表: # file # decimal.Context # thread.LockType # threading.Lock # threading.RLock # threading.Condition # threading.Semaphore # threading.BoundedSemaphore #with open('Dave.txt', 'r') as f: # for eachLine in f: # print(eachLine) #它会完成准备工作,比如试图打开一个文件,如果一切正常,把文件对象赋值给f.然后用迭代器遍历文件中的每一行,当 #完成时,关闭文件.无论的在这一段代码的开始,中间,还是结束时发生异常,会执行清理的代码,此外文件仍会被自动的关闭. ## ************ Part 5: 触发异常 ***************** #Python 提供了一种机制让程序员明确的触发异常:这就是raise 语句. ##### raise 语句 #在Python 3.2 的联机文档说明:6.8. The raise statement #raise_stmt ::= "raise" [expression ["from" expression]] #Python 2.x rasie语法: #raise [SomeException [, args [, traceback]]] #第一个参数,SomeExcpetion,是触发异常的名字.如果有,它必须是一个字符串,类或实例. #如果有其他参数(arg 或traceback),就必须提供SomeExcpetion #第二个符号为可选的args(比如参数,值),来传给异常.这可以是一个单独的对象也可以是一个 #对象的元组.当异常发生时,异常的参数总是作为一个元组传入.如果args 原本就是元组,那么就将其 #传给异常去处理;如果args 是一个单独的对象,就生成只有一个元素的元组(就是单元素元组).大多 #数情况下,单一的字符串用来指示错误的原因.如果传的是元组,通常的组成是一个错误字符串,一个 #错误编号,可能还有一个错误的地址,比如文件,等等. #最后一项参数,traceback,同样是可选的(实际上很少用它),如果有的话,则是当异常触发时新 #生成的一个用于异常-正常化(exception—normally)的追踪(traceback)对象.当你想重新引发异常 #时,第三个参数很有用(可以用来区分先前和当前的位置).如果没有这个参数,就填写None. # #最常见的用法为SomeException 是一个类.不需要其他的参数,但如果有的话,可以是一个单一对 #象参数,一个参数的元组,或一个异常类的实例.如果参数是一个实例,可以由给出的类及其派生类实 #例化(已存在异常类的子集).若参数为实例,则不能有更多的其他参数. #try: # print(1 / 0) #except: # raise RuntimeError("Something bad happened") #-->RuntimeError: Something bad happened ## ************ Part 6: 断言 ***************** #断言是一句必须等价于布尔真的判定;此外,发生异常也意味着表达式为假. ### 断言语句 #断言语句等价于这样的Python 表达式,如果断言成功不采取任何措施(类似语句),否则触发 #AssertionError(断言错误)的异常.assert 的语法如下: # assert expression[, arguments] #下面有一些演示assert 用法的语句: #assert 1 == 1 #assert 2 + 2 == 2 * 2 #assert len(['my list', 12]) < 10 #-->以上三个正常 #assert range(3) == [0, 1, 2] #-->最后一个报错:AssertionError #AssertionError 异常和其他的异常一样可以用try-except 语句块捕捉,但是如果没有捕捉,它将 #终止程序运行而且提供一个如下的traceback: #用try-except 语句捕获AssertionError 异常: #try: # assert 1 == 0, 'One does not equal zero silly!' #except AssertionError as args: # print('%s: %s' % (args.__class__.__name__, args)) #-->AssertionError: One does not equal zero silly! #通过这个例子可以很好的看出什么是assert,即我们假定一个条件,当为false 时,则触发assertionerror。 ## ************ Part 7: 标准异常 ***************** #所有的标准/内建异常都是从根异常派生的.目前,有3 个直接从BaseException 派生的异常子 #类:SystemExit,KeyboardInterrupt 和Exception.其他的所有的内建异常都是Exception 的子类. #In Python, all exceptions must be instances of a class that derives from BaseException #The class hierarchy for built-in exceptions is: # #BaseException # +-- SystemExit # +-- KeyboardInterrupt # +-- GeneratorExit # +-- Exception # +-- StopIteration # +-- ArithmeticError # | +-- FloatingPointError # | +-- OverflowError # | +-- ZeroDivisionError # +-- AssertionError # +-- AttributeError # +-- BufferError # +-- EnvironmentError # | +-- IOError # | +-- OSError # | +-- WindowsError (Windows) # | +-- VMSError (VMS) # +-- EOFError # +-- ImportError # +-- LookupError # | +-- IndexError # | +-- KeyError # +-- MemoryError # +-- NameError # | +-- UnboundLocalError # +-- ReferenceError # +-- RuntimeError # | +-- NotImplementedError # +-- SyntaxError # | +-- IndentationError # | +-- TabError # +-- SystemError # +-- TypeError # +-- ValueError # | +-- UnicodeError # | +-- UnicodeDecodeError # | +-- UnicodeEncodeError # | +-- UnicodeTranslateError # +-- Warning # +-- DeprecationWarning # +-- PendingDeprecationWarning # +-- RuntimeWarning # +-- SyntaxWarning # +-- UserWarning # +-- FutureWarning # +-- ImportWarning # +-- UnicodeWarning # +-- BytesWarning # +-- ResourceWarning ## ************ Part 8: 异常和sys 模块 ***************** #另一种获取异常信息的途径是通过sys 模块中exc_info()函数. 此功能提供了一个3 元组 #(3-tuple)的信息, 多于我们单纯用异常参数所能获得. 让我们看看如何用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 0x00000000022185C8>) #for eachItem in exc_tuple: # print(eachItem) #--> #<class 'ValueError'> #could not convert string to float: 'abc123' #<traceback object at 0x0000000002156E48> #我们从sys.exc_info()得到的元组中是: # exc_type: 异常类 # exc_value: 异常类的实例 # exc_traceback: 追踪(traceback)对象 #前两项:实际的异常类, 和这个异常类的实例. 第三项, 是一个新增的追踪(traceback)对象. 这一对象提供了的发生异常的上下文. #它包含诸如代码的执行帧,异常发生时的行号等信息.