# coding:utf-8


#什么是异常

#Python用异常对象来表示异常情况。遇到错误后,会引发异常。如果异常对象并未被处理或捕捉,程序就会用所谓的回溯(traceback,一种错误信息)终止执行。

1/0

#Traceback (most recent call last):

#  File "<input>", line 1, in <module>

#ZeroDivisionError: integer division or modulo by zero

#事实上,每个异常都是一些类(本例中是ZeroDivisionError)的实例,这些实例可以被引发,并且可以用很多种方法进行捕捉,使得程序可以捉住错误并且对其进行处理,而不是让整个程序失效。

#按自己的方式出错

#在学习如何处理异常之前,先看一下自己如何引发异常,以及创建自己的异常类型。


# raise语句

#为了引发异常,可以使用一个类(应该是Exception的子类)或者实例参数调用raise语句。

raise Exception

#Traceback (most recent call last):

#  File "<input>", line 1, in <module>

#Exception

raise Exception('hyperdrive overload')

#Traceback (most recent call last):

#  File "<input>", line 1, in <module>

#Exception: hyperdrive overload

#第一个例子raise Exception引发了一个没有任何有关错误信息的普通异常。后一个例子中,则添加了错误信息hyperdrive overload。

#内建的异常种类有很多。这些内建异常都可以在exceptions模块(和内建的命名空间)中找到。可以使用dir函数列出模块的内容。

import exceptions

dir(exceptions)

#这个名单会很长,所有这些异常都可以用在raise语句中:

raise ArithmeticError

#Traceback (most recent call last):

#  File "<input>", line 1, in <module>

#ArithmeticError


#一些最重要的内建异常类:

#Exception              所有异常的基类

#AttributeError         特性引用或赋值失败时引用

#IOError                试图打开不存在文件(包括其他情况)时引发

#IndexError             在使用序列中不存在的索引时引发

#KeyError               在使用映射中不存在的键时引发

#NameError              在找不到名字(变量)时引发

#SyntaxError            在代码为错误形式时引发

#TypeError              在内建操作或者函数应用于错误类型的对象时引发

#ValueError             在内建操作或者函数应用于正确类型的对象,但是该对象使用不合适的值时引发

#ZeroDivisionError      在除法或者摸除操作的第二个参数为0时引发


#自定义异常类

#如何创建自己的异常类?就像其他类一样,只是要确保从Exception类继承(不管是间接的或者是直接的,也就是说继承其他的内建异常类也是可以的)。

#那么编写一个自定义异常类基本上就像下面这样:

#Class SomeCustomException(Exception): pass


#捕捉异常

#可以使用try/except语句来实现

x = input('Enter the first number: ')

y = input('Enter the second number: ')

print x / y

#程序工作正常,假如用户输入0作为第二个数:

#Enter the first number: >? 10

#Enter the second number: >? 0

#Traceback (most recent call last):

#  File "<input>", line 3, in <module>

#ZeroDivisionError: integer division or modulo by zero

#为了捕捉异常并且做出一些错误处理,可以这样重写程序:

try:

   x = input('Enter the first number: ')

   y = input('Enter the second number: ')

   print x/y

except ZeroDivisionError:

   print "The second number can't be zero!"

#Enter the first number: >? 10

#Enter the second number: >? 0

#The second number can't be zero!

#其实也可以用if语句检查y值会简单些,但如果需要给程序假如更多除法,那么就得给每个除法加个if语句。而使用try/except的话只需要一个错误处理器。


#打开和关闭屏蔽异常

#考虑一下一个能“屏蔽”ZeroDivisionError(除零错误)的计算器类。如果这个行为被激活,那么计算器就会打印错误信息,而不是让异常传播。

class MuffledCalculator:

   muffled = False         #异常屏蔽的开关

   def calc(self, expr):

       try:

           return eval(expr)

       except ZeroDivisionError:

           if self.muffled:            #判断开关,是否要打开异常还是自定义错误信息

               print 'Division by zero is illegal'     #自定义错误信息

           else:

               raise                       #异常

calculator = MuffledCalculator()

print calculator.calc('10/2')

#5

calculator.muffled = False

calculator.calc('10/0')

#Traceback (most recent call last):

#  File "<input>", line 2, in <module>

#  File "<input>", line 5, in calc

#  File "<string>", line 1, in <module>

#ZeroDivisionError: integer division or modulo by zero

calculator.muffled = True

calculator.calc('10/0')

#Division by zero is illegal


#不止一个except子句

#如果运行上一节的程序并且在提示符后面输入非数字类型的值,就会产生另外一个异常:

x = input('Enter the first number: ')

y = input('Enter the second number: ')

print x / y

#Enter the first number: >? 10

#Enter the second number: >? "Hello, world!"

#Traceback (most recent call last):

#  File "<input>", line 3, in <module>

#TypeError: unsupported operand type(s) for /: 'int' and 'str'

#因为except子句只寻找ZeroDivisionError异常,这次的错误就溜过了检查并导致程序终止

#为了捕捉这个异常,可以直接在同一个try/except语句后面加上另一个except子句:

try:

   x = input('Enter the first number: ')

   y = input('Enter the second number: ')

   print x/y

except ZeroDivisionError:

   print "The second number can't be zero!"

except TypeError:

   print "That wasn't a number, was it?"

#Enter the first number: >? 10

#Enter the second number: >? "Hello world!"

#That wasn't a number, was it?

#这次用if语句实现可就复杂了,应该注意到,异常处理并不会搞乱原来的代码,而增加一大堆if语句检查可能的错误情况会让代码相当难读。


#用一个块捕捉两个异常

#如果需要用一个块捕捉多个类型异常,那么可以将它们作为元组列出,像下面这样:

try:

   x = input('Enter the first number: ')

   y = input('Enter the second number: ')

   print x/y

except (ZeroDivisionError, TypeError, NameError):

   print 'Your numbers were bogus...'

#Enter the first number: >? 10

#Enter the second number: >? 0

#Your numbers were bogus...

#上面代码中,如果用户输入字符串或者其他类型的值,而不是数字,或者第2个数为0,都会打印同样的错误信息。


#捕捉对象

#如果希望在except子句中访问异常对象,可以使用参数(注意,就算要捕捉到多个异常,也只需向except子句提供一个参数---一个元组)。

#比如想让程序继续运行,但是又因为某种原因想记录下错误(比如只是打印给用户看),这个功能就很有用。

#下面的示例程序会打印异常(如果发生的话),但是程序会继续运行:

try:

   x = input('Enter the first number: ')

   y = input('Enter the second number: ')

   print x/y

except (ZeroDivisionError, TypeError), e:

   print e

#Enter the first number: >? 10

#Enter the second number: >? 0

#integer division or modulo by zero

#Enter the first number: >? 10

#Enter the second number: >? 'Hello'

#unsupported operand type(s) for /: 'int' and 'str'


#真正的捕捉

#就算程序能处理好几种类型的异常,但有些异常还会从眼皮底下溜走。比如还用那个除法程序来举例,在提示符下面直接按回车,不输入任何东西,会得到一个类似下面这样的错误信息。

#Traceback (most recent call last):

#  File "<input>", line 2, in <module>

#  File "<string>", line 0

#    ^

#SyntaxError: unexpected EOF while parsing

#如果真的想用一段代码捕捉所有异常,那么可以在except子句中忽略所有的异常类:

try:

   x = input('Enter the first number: ')

   y = input('Enter the second number: ')

   print x/y

except:

   print 'Something wrong happend...'

#Enter the first number: >? 'Hello'

#Enter the second number: >? '123'

#Something wrong happend...


#else子句

#可以对条件和循环语句那样,给try/except语句加个else子句:

try:

   print 'A simple task'

except:

   print 'What? Something went wrong?'

else:

   print 'Ah...It went as planned.'

#执行结果:

#A simple task

#Ah...It went as planned.


#加入到循环语句

while True:

   try:

       x = input('Enter the first number: ')

       y = input('Enter the second number: ')

       value = x/y

       print 'x/y is', value

   except:

       print 'Invalid input. Please try again.'

   else:

       break

#这里的循环只在没有异常引发的情况下才会退出(由else子句中的break语句退出)。

#Enter the first number: >? 1

#Enter the second number: >? 0

#Invalid input. Please try again.

#Enter the first number: >? 'foo'

#Enter the second number: >? 'bar'

#Invalid input. Please try again.

#Enter the first number: >? baz

#Invalid input. Please try again.

#Enter the first number: >? 10

#Enter the second number: >? 2

#x/y is 5

#之前提到过,可以使用空的except子句来捕捉所有Exception类的异常(也会捕捉其所有子类的异常)。百分之百捕捉到所有的异常是不可能的,因为try/except语句中的代码可能会出问题。

#不过如果使用excpet Exception的话,可以使用之前除法程序中打印更加有用的错误信息:

while True:

   try:

       x = input('Enter the first number: ')

       y = input('Enter the second number: ')

       value = x/y

       print 'x/y is', value

   except Exception, e:

       print 'Invalid input:', e

       print 'Please try again'

   else:

       break

#Enter the first number: >? 1

#Enter the second number: >? 0

#Invalid input: integer division or modulo by zero

#Please try again

#Enter the first number: >? 'x'

#Enter the second number: >? 'y'

#Invalid input: unsupported operand type(s) for /: 'str' and 'str'

#Please try again

#Enter the first number: >? quuux

#Invalid input: name 'quuux' is not defined

#Please try again

#Enter the first number: >? 10

#Enter the second number: >? 2

#x/y is 5


#finally子句

#它可以用来在可能异常后进行清理。它和try子句联合使用:

x = None

try:

   x = 1/0          #这句会报错

finally:            #不会因为错误终止,会继续执行下面语句

   print 'Cleaning up...'

   del x

#上面代码中,finally子句肯定会被执行,不管try子句中是否发生异常。

#在try子句之前,初始化x的原因是如果不这样做,由于ZeroDivisionError的存在,x就永远不会被赋值。这样就会导致在finally子句中使用del删除它的时候异常,而且这个异常是无法捕捉的。

#运行这段代码,在程序崩溃之前,对于变量x的清理就完成了:

#Cleaning up...

#Traceback (most recent call last):

#  File "<input>", line 3, in <module>

#ZeroDivisionError: integer division or modulo by zero

#因为使用del语句删除一个变量是非常不负责的清理手段,所以finally子句用于关闭文件或者网络套接字时会非常有用。

#还可以在同一条语句中组合使用try,except,finally和else。

try:

   1/0

except NameError:

   print "Unknown variable"

else:

   print "That went well!"

finally:

   print "Cleaning up."


#异常和函数

#异常和函数能很自然的一起工作。如果异常在函数内引发而不被处理,它会传播至函数调用的地方。如果在那里也没有处理异常,它会继续传播,一直到达主程序(全局作用域)。如果那里没有异常处理程序,程序会带着栈跟踪中止。

#例子:

def faulty():

   raise Exception('Something is wrong')

def ignore_exception():

   faulty()

def handle_exception():

   try:

       faulty()

   except:

       print 'Exception handled'

ignore_exception()

#Traceback (most recent call last):

#  File "<input>", line 11, in <module>

#  File "<input>", line 4, in ignore_exception

#  File "<input>", line 2, in faulty

#Exception: Something is wrong

#faulty中产生的异常通过faulty和ignore_exception传播,最终导致了栈跟踪。

handle_exception()

#Exception handled

#它传播到handle_exception,但在这个函数中被try/except语句处理。


#异常之禅

#有些时候,条件语句可以实现和异常处理同样的功能,但是条件语句可能在自然性和可读性上差些。而从另一方面看,某些程序中使用if/else实现会比用try/except要好。

#让我们看看几个例子。

#假设有一个字典,我们希望打印出存储在特定的键下面的值。如果该键不存在,那么什么也不作。代码可能像下面这样写:

def describePerson(person):

   print 'Description of', person['name']

   print 'Age:', person['age']

   if 'occupation' in person:

       print 'Occupation:', person['occupation']

#如果给程序提供包含名字tom和年龄35(没有职业)的字典的函数

tom = {'name':'tom', 'age':'35'}

describePerson(tom)

#会得到如下输出:

#Description of tom

#Age: 35

#如果添加了职业IT

tom = {'name':'tom', 'age':'35','occupation':'IT'}

describePerson(tom)

#会得到如下输出:

#Description of tom

#Age: 35

#Occupation: IT

#代码非常直观,但是效果不高。程序会两次查找'occupation'键,其中一次用来查看键是否存在,另外一次获得值(打印)。另外一个解决方案如下:

def describePerson(person):

   print 'Description of', person['name']

   print 'Age:', person['age']

   try:

       print 'Occupation:' + person['occupation']

   except KeyError: pass

#这个程序直接假定'occupation'键存在。如果它的确存在,直接取出它的值打印输出即可---不用额外检查它是否真的存在。

tom = {'name':'tom', 'age':'35'}

describePerson(tom)

tom = {'name':'tom', 'age':'35','occupation':'IT'}

describePerson(tom)

#在查看对象是否存在特定特性时,try/except也很有用。假设想要查看某对象是否有write特性,那么可以使用如下代码:

try:

   obj.write

except AttributeError:

   print 'The object is not writeable'

else:

   print 'The object is writeable'

#这里的try子句仅仅访问特性而不用对它做别的有用的事情。如果AttributeError异常引发,就证明对象没有这个特性,反之存在该特性。

#在很多情况下,使用try/except语句比使用if/else会更自然一些,应该养成尽可能使用try/except的习惯。