Python学习笔记【八】——《python基础教程》:: 异常

8. 异常

8.1. 什么是异常

  Python用异常对象(exception object)来表示异常情况。遇到错误后,会引发异常。若异常对象未被处理或捕捉,程序会用回溯(Traceback,一种错误信息)终止执行,例如:

>>> 1/0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero

  事实上,每个异常都是一些类的实例,可以被引发也可以被捕捉。当捕捉到这些异常并对其进行处理,可使程序继续运行。

8.2. 按自己的方式出错

  学习处理异常之前,先学习如何引发异常,以及自定义异常类型。  

8.2.1. raise语句

  可以使用一个类(Exception的子类)或实例参数调用raise语句来引发异常。当使用类时,程序会自动创建实例。例如:

#引发没有错误信息的普通异常
>>> raise Exception
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
Exception
#添加错误信息的异常
>>> raise Exception("hyperdrive overload")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
Exception: hyperdrive overload

  内建的异常类有很多种(参考Python库参考手册的“Build-in Exception”一节),这些异常都可以在exceptions模块(和内建的命名空间)中找到,所有这些异常都可以用在raise语句中。

>>> import exceptions
>>> dir(exceptions)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BufferError', 'BytesWarning', 'DeprecationWarning', 'EOFError', 'EnvironmentError', 'Exception', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError', 'NotImplementedError', 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'ReferenceError', 'RuntimeError', 'RuntimeWarning', 'StandardError', 'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'TabError', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError', '__doc__', '__name__', '__package__']

  下表描述了一些重要的内建异常类:

类名描述
Exception所有异常的基类
AttributeError特性引用或赋值失败时引发
IOError试图打开不存在文件(包括其他情况)时引发
IndexError在使用序列中不存在的索引时引发
KeyError在使用映射中不存在的键时引发
NameError再找不到名字(变量)时引发
SyntaxError在代码为错误形式时引发
TypeError在内建操作或者函数应用于错误类型的对象时引发
ValueError在内建操作或者函数应用于正确类型的对象,但是该对象使用不合适的值时引发
ZeroDivisionError在除法或者模除操作的第二个参数为0时引发

8.2.2. 自定义异常类

  自定义异常类可以根据异常所在的类,选择性地处理当前类型的异常。
  创建异常类与创建其他类方法一样,只需确保从Exception类继承即可。

8.3. 捕捉异常

  可以使用try/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!"
... 
enter the first number: 10
enter the second number: 0
The second number can't be zero!

  上述异常也可以通过if语句检查y值实现,但是try/except有以下优势:
  1. 若出现多个除法运算,需要对所有运算进行if判断,而是用try/except只需要一个错误处理器;
  2. try/except不会搞乱原来的代码,而if语句检查可能错误会降低代码可读性。
  ★若异常没有被捕捉,将会被传递到调用的函数中,直到程序最顶层
  
  若捕捉到的函数想重新引发,可以使用不带参数的raise。例如,定义一个可以屏蔽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()
>>> calculator.calc("10/2")
5
>>> calculator.calc("10/0") #屏蔽未打开,捕捉并重新引发ZeroDivisionError异常
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", 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

8.4. 不止一个except子句

  8.3节中的第一个例子,若在提示符后面输入非数字类型的值,会产生另一个异常:

enter the first number: w
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<string>", line 1, in <module>
NameError: name 'w' is not defined

  可以在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: 23
enter the second number: "That wasn't a number, was it?"
That wasn't a number, was it?

8.5. 用一个块捕捉两个异常

  若要用一个块捕捉多个异常类型,可以将它们作为元组列出。例如:  

>>> 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..."
... 

8.6. 捕捉对象

  若希望在except子句中访问异常对象本身,可以使用两个参数。例如,如果希望程序继续运行,但是又想记录错误时,可能用到该功能。

>>> try:
...   x=input('enter the first number: ')
...   y=input('enter the second number: ')
...   print x/y
... except (ZeroDivisionError, TypeError, NameError), e:
...   print e
... 
enter the first number: 34
enter the second number: aa
name 'aa' is not defined

8.7. 真正的全捕捉

  可以使用try/except指定要捕捉的异常类型,但是,程序员无法预测所有可能发生的异常,总会有所遗漏。可以在except子句中忽略所有的异常类,以实现捕捉所有异常:

>>> try:
...   x=input('enter the first number: ')
...   y=input('enter the second number: ')
...   print x/y
... except:
...   print "Something wrong happened..."
... 
enter the first number: 34
enter the second number: a
Something wrong happened...

!!!捕捉所有异常类是危险的,他会隐藏所有程序员未想到并且为做好准备处理的错误。甚至会捕捉用户中止执行的Ctrl+C操作,以及用sys.exit函数终止程序的操作。此时可以使用except Exception, e,或者对异常对象e进行一些检查。
  

8.8. 万事大吉

  之前的例子中,发生异常后往往只打印一个错误信息,对于产生的异常没有实际修正作用。上述例子可以像对条件和循环语句那样,给try/except添加else子句实现循环,实现只有在没有异常情况下退出,否则重新执行异常语句的功能。例如:

>>> while True:
...   try:
...     x=input("enter the first number: ")
...     y=input("enter the second number: ")
...     print x/y
...   except Exception, e: #捕获所有异常并保存到e
...     print "invalid input: ",e #打印捕获的异常
...     print "Please input again" #若有异常,则重新执行
...   else: #若没有异常,只想下面一行代码,退出循环
...     break
... 
enter the first number: 1
enter the second number: 0
invalid input:  integer division or modulo by zero
Please input again
enter the first number: dds
invalid input:  name 'dds' is not defined
Please input again
enter the first number: "asd"
enter the second number: "nmm"
invalid input:  unsupported operand type(s) for /: 'str' and 'str'
Please input again
enter the first number: 10
enter the second number: 2
5

8.9. 最后…

  finally子句,用于try/except之后的清理工作,无论是否发生异常,都会执行finally子句。例如:
  

>>> try:
...   1/1   #无异常发生时
... except:
...   print "except"
... finally:
...   print "finally"
... 
1
finally
>>> try:
...   1/0   #有异常发生时
... except:
...   print "except"
... finally:
...   print "finally"
... 
except
finally

  finally子句在代码执行后关闭文件或者套接字是非常有用。
  
  在Python2.5之前,finally子句需要独立使用,不能作为try/except子句使用。在Python2.5及其之后版本中,可以与try/except/else/finally组合使用。

8.10. 异常和函数

  若异常在函数内引发而不被处理,它就会被传递到函数调用的地方。若调用处没有被处理,它会继续传播,一直到达主程序(全局作用域)。若依然没有被处理,程序会带着堆栈跟踪中止。例如:
  

>>> def faulty():
...   raise Exception("Something is wrong")
... 
>>> def ignore_faulty():
...   faulty()
... 
>>> def handle_exception():
...   try:
...     faulty()
...   except:
...     print "Exception handled"
... 
>>> ignore_faulty() #faulty的异常传递到ignore_faulty,未处理异常,导致堆栈跟踪
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in ignore_faulty
  File "<stdin>", line 2, in faulty
Exception: Something is wrong
>>> handle_exception() #faulty的异常传递到handle_exception,被处理
Exception handled

8.11. 异常之禅

  异常处理并不是很复杂。若知道某段代码可能导致某种异常,又不希望程序已堆栈跟踪的形式终止,可以根据需要添加try/except或try/finally语句进行处理。
  有时条件语句可以实现和异常处理同样的功能,但条件语句可能在自然性和可读性上差些。另一方面看,某程序使用if/else实现会比使用try/except要好。

  举个例子对比下if/else和try/except。假设有一个字典,希望打印出存储在特定的键下面的值。若不存在,则什么也不做。
  可以使用if/else实现,编码如下:

>>> def describePerson(person):
...   print "Description of", person["name"]
...   print "Age:", person["age"]
...   if "occupation" in person:
...     print "Occupation:", person["occupation"]
#提供名字和年龄字典时的输出
>>> holly={"name":"holly","age":55}
>>> describePerson(holly)
Description of holly
Age: 55
#提供名字、年龄和职业时的输出
>>> holly={"name":"holly","age":55, "occupation":"software engineer"}
>>> describePerson(holly)
Description of holly
Age: 55
Occupation: software engineer

  上述代码直观,但效率低。程序会两次查找“occupation”键——判断是否存在;获取键值。
  可以采用try/except语句实现,编码如下:

>>> def describePerson(person):
...   print "Description of", person["name"]
...   print "Age:", person["age"]
...   try:
...     print "Occupation:" + person["occupation"]#此处使用加号连接,若使用逗号,发生异常时"occupation:"会被输出
...   except KeyError: pass
... 
#提供名字和年龄字典时的输出
>>> holly={"name":"holly","age":55}
>>> describePerson(holly)
Description of holly
Age: 55
#提供名字、年龄和职业时的输出
>>> holly={"name":"holly","age":55, "occupation":"software engineer"}
>>> describePerson(holly)
Description of holly
Age: 55
Occupation:software engineer

  程序假定”occupation”键存在,若真实存在,则直接输出打印;否则会引发KeyError异常,被except捕捉处理。
  
  另一个例子,展示下try/except在查看对象是否存在某特性中的作用。假设要查看某对象是否有write特性,编码如下:

>>> try:
...   obj.write
... except AttributeError:
...   print "The object is not writeable"
... else:
...   print "The objece is writebale"

  try子句访问特性,若引发AttributeError异常,说明对象没这个特性,反之有。这跟getattr方法功能相同,事实上getattr内部实现也是使用了该方法。

上述代码获得的效率提高并不多,除非程序对性能有明确需求,否则开发人员不需要过多考虑优化的问题。
很多情况下,使用try/except比使用if/else更加”Python化”,应养成尽可能使用try/except语句。
try/except语句在Python中的表现可以用海军少将Grace Hooper的妙语解释:”请求宽恕易于请求许可”。在做一件事时去处理可能出现的错误,而不是在开始做事前就进行大量的检查,这个可略可以总结为习语”看钱就调(Leap Before You Look)”。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值