Python学习笔记之九——错误与异常

Python 有两种常见的错误:语法错误和异常。

语法错误

语法错误也称之为解析错误。例如:

>>> while True print('Hello world') 
  File "", line 1, in ? 
    while True print('Hello world') 
                   ^ 
SyntaxError: invalid syntax 

语法分析器指出了出错的一行,并且在最先找到的错误的位置标记了一个小小的’>箭头’。错误是由箭头前面的标记引起的。在这个例子中,检测到错误发生在函数print(),因为在它之前缺少一个冒号(‘:’)。文件名和行号会一并输出。

异常

即使一条语句或表达式在语法上是正确的,在运行它的时候,也有可能发生错误。在执行期间检测到的错误被称为异常(程序并没有崩溃) 大多数异常都不会被程序处理,导致产生类似下面的错误信息:

>>> 10 * (1/0) 
Traceback (most recent call last): 
  File "", line 1, in ? 
ZeroDivisionError: division by zero 
>>> 4 + spam*3 
Traceback (most recent call last): 
  File "", line 1, in ? 
NameError: name 'spam' is not defined 
>>> '2' + 2 
Traceback (most recent call last): 
  File "", line 1, in ? 
TypeError: Can't convert 'int' object to str implicitly 

最后的一行错误信息指示异常的原因。异常有不同的类型,其类型会作为消息的一部分打印出来:在这个例子中的类型有 ZeroDivisionError、NameError 和TypeError,打印出来的异常类型的字符串就是内置的异常的名称。用户也可以自定义异常,标准异常的名称都是内置的标识符(不是保留的关键字)。
异常文本最后一部分给出了异常的详细信息和引起异常的原因。
错误信息的前面部分以堆栈回溯的形式显示了异常发生的上下文。通常调用堆栈里会包含源代码的行信息,但是来自标准输入的源码不会显示行信息。
内置的异常列出了内置的异常以及它们的含义。

抛出异常

可以通过编程来选择处理部分异常。下面的例子会一直要求用户输入直到输入一个合法的整数为止,但允许用户中断这个程序(使用 Control-C 或系统支持的任何方法);注意用户产生的中断引发的是KeyboardInterrupt 异常。

>>> while True: 
...     try: 
...         x = int(input("Please enter a number: ")) 
...         break 
...     except ValueError: 
...         print("Oops!  That was no valid number.  Try again...") 
... 

Try 语句按以下方式工作:
首先,执行try 子句(try 和关键字之间的语句)
如果未发生任何异常,忽略except 子句且try 语句执行完毕。
如果在try 子句执行过程中发生异常,跳过该子句的其余部分,如果异常的类型与except 关键字后面的异常名匹配,则执行except 子句,然后继续执行try语句之后的代码
如果异常的类型与except 关键字后面的异常名不匹配,它将被传递给上层的try语句;如果没有找到处理异常的代码,它就会成为一个未处理异常,程序会终止运行并显示一条错误信息。
Try 语句可能有多个子句,以指定不同的异常处理程序。不过至多只有一个处理程序将被执行。处理程序只处理发生在相应try 子句中的异常,不会处理同一个try 子句的其他处理程序中发生的异常。一个except 子句可以用带括号的元组列出多个异常的名字。例如:

... except (RuntimeError, TypeError, NameError): 
...     pass 

最后一个except 子句可以省略异常名称(相当于else 一样,异常类型都不是上面的类型,最后一个except 统一处理),但使用这种方式的时候一定要特别小心,因为它会隐藏一个真实的程序错误,可能会引起其它异常,让调用者也去处理这个异常:

import sys
try: 
    f = open('myfile.txt') 
    s = f.readline() 
    i = int(s.strip()) 
except OSError as err: 
    print("OS 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 语句有一个可选的else 子句,其出现时,必须放在所有except 子句的后面。主要用于当try 语句没有抛出异常时执行一些代码。

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() 

当异常发生时,它可能带有相关数据,也称为异常的参数。参数的有无和类型取决于异常的类型。
except 子句可以在异常的名称之后指定一个变量,这个变量将绑定于一个异常实例,同时异常的参数将存放在实例的args 中。为方便起见,异常实例中定义了一个str()方法,因此异常的参数可以直接打印而不必引用.args。也可以在引发异常之前实例化一个异常,然后向它添加任何想要的属性:

>>> try: 
...    raise Exception('spam', 'eggs') 
... except Exception as inst: 
...    print(type(inst))    # the exception instance 
...    print(inst.args)     # arguments stored in .args 
...    print(inst)          # __str__ allows args to be printed directly, 
...                         # but may be overridden in exception subclasses 
...    x, y = inst.args     # unpack args 
...    print('x =', x) 
...    print('y =', y) 
... 
'Exception'> 
('spam', 'eggs') 
x = spam 
y = eggs 

这里的 raise 表示引发一个指定的异常。
对于未处理的异常,如果它含有参数,那么参数会作为异常信息的最后一部分打印出来。
异常处理程序不仅处理直接发生在try 子句中的异常,而且还处理try 子句调用的函数(甚至间接调用的函数)引发的异常。例如:

>>> def this_fails(): 
...     x = 1/0 
...
>>> try: 
...     this_fails() 
... except ZeroDivisionError as err: 
...     print('Handling run-time error:', err) 
... 
Handling run-time error: int division or modulo by zero

引发异常

raise 语句允许程序员强行引发一个指定的异常。例如:

>>> raise NameError('HiThere') 
Traceback (most recent call last): 
  File "", line 1, in ? 
NameError: HiThere 

raise 的唯一参数指示要引发的异常。它必须是一个异常实例或异常类(从Exception 派生的类)。
如果需要引发一个异常,但不打算处理它,一个简单的raise 语句就可以重新引发异常:

>>> try: 
...     raise NameError('HiThere') 
... except NameError: 
...     print('An exception flew by!') 
...     raise 
... 
An exception flew by! 
Traceback (most recent call last): 
File "", line 2, in ? 
NameError: HiThere

用户定义的异常

程序可以通过创建新的异常类来命名自己的异常。异常通常应该继承Exception类,直接继承或者间接继承都可以。例如:

>>> class MyError(Exception): 
...     def __init__(self, value): 
...         self.value = value 
...     def __str__(self): 
...         return repr(self.value) 
... 
>>> try: 
...     raise MyError(2*2) 
... except MyError as e: 
...     print('My exception occurred, value:', e.value) 
... 
My exception occurred, value: 4 
>>> raise MyError('oops!') 
Traceback (most recent call last): 
  File "", line 1, in ? 
__main__.MyError: 'oops!' 

在示例中,Exception 默认的init() 方法被自定义的MyError 重写了,新的__init() 方法简单地创建了value属性,这将替换默认的创建args属性的行为。
异常类可以像其他类一样做任何事情,但是通常都会比较简单,只提供一些属性以允许异常处理程序获取错误相关的信息。创建一个能够引发几种不同错误的模块时,一个通常的做法是为该模块定义的异常创建一个基类,然后基于这个基类为不同的错误情况创建特定的子类:

class Error(Exception): 
    """Base class for exceptions in this module.""" 
    pass 

class InputError(Error): 
    """Exception raised for errors in the input. 

    Attributes: 
        expression -- input expression in which the error occurred 
        message -- explanation of the error 
    """ 
    def __init__(self, expression, message): 
        self.expression = expression 
        self.message = message
class TransitionError(Error): 
    """Raised when an operation attempts a state transition that's not 
    allowed. 

    Attributes: 
        previous -- state at beginning of transition 
        next -- attempted new state 
        message -- explanation of why the specific transition is not allowed 
    """ 

    def __init__(self, previous, next, message): 
        self.previous = previous 
        self.next = next 
        self.message = message 

大多数异常的名字都以”Error” 结尾,类似于标准异常的命名。
很多标准模块中都定义了自己的异常来报告在它们所定义的函数中可能发生的错误。类 这
一章给出了类的详细信息。

定义finally操作

Try 语句有另外一个可选的finally 子句,目的在于不管有没有发生异常,在离开try 语句之前总是执行finally 子句。当try 子句中发生了一个异常,并且没有except 语句处理(或者异常发生在except 或else 子句中),在执行完finally 子句之后将重新引发这个异常。try 语句由于break、contine或return 语句离开时,同样会执行finally 子句。
例如:

>>> def divide(x,y):
...     try:
...         result = x/y
...     except ZeroDivisionError:
...         print("division by zero!")
...     else:
...         print("relsut is",result)
...     finally:
...         print("executing finally clause")
... 
>>> divide(2,1)
relsut is 2.0
executing finally clause
>>> divide(2,0)
division by zero!
executing finally clause
>>> divide("2","1")
executing finally clause
Traceback (most recent call last):
  File "", line 1, in 
  File "", line 3, in divide
TypeError: unsupported operand type(s) for /: 'str' and 'str'
>>> 

正如您所看到的,在任何情况下都会执行finally 子句,由两个字符串相除引发的TypeError 异常没有被except 子句处理,因此在执行finally 子句后被重新引发。
在真实的应用程序中,finally 子句用于释放外部资源(例如文件或网络连接),无论这个资源的使用是否成功。

自动释放资源的相关预定义

有些对象定义了在不需要该对象时释放这个资源的相关操作,无论这个对象的使用是成功还是失败。下面的示例,它尝试打开一个文件并打印其内容到屏幕。

for line in open("myfile.txt"): 
    print(line, end="")

这段代码的问题就是这部分代码执行完之后它还会让文件在一段不确定的时间内保持打开状态。这在简单的脚本中没什么,但是在大型应用程序中可能是一个问题。With语句可以确保像文件这样的对象资源总能及时准确地被释放掉。

with open("myfile.txt") as f: 
    for line in f: 
        print(line, end="") 

这和C#中的using 语法是类似的。
执行该语句后,文件f 将始终被关闭,即使在处理某一行时遇到了问题。

你可能感兴趣的:(Python)