Python教程之七-----错误和异常

到现在为止错误消息还没有被提及,但是如果你尝试过之前的代码应该已经见过一些。有2种(至少)可以分辨的错误类型:语法错误和异常

8.1 语法错误

语法错误,同样称为解析错误,可能是在你学习Python中最常见的一种抱怨了:

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

解析器重复那个讨厌的行并且显示了一个小的‘箭头’在检测到错误的最开始的点上。错误是由箭头之前的记号引起的(或者检测到):在上例中,错误在print()函数中检测到,因为之前少了一个冒号(':')。文件名和行数被打印出来,这样你能知道在哪里可以看到这个情况。

8.2 异常

即使一个语句或者表达式在语法上是正确的,当尝试执行它时也可能引起一个错误。在运行时检测到的错误称为异常并且不是无条件致命的:稍后你将学会如何在Python程序中处理他们。大部分异常不能被程序如理,然而,错误消息中的结果如下:

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

错误消息的最后一行表明发生的事。异常有不同的类型,并且这个类型也被作为消息的一部分打印出来:在上例中的类型有:ZeroDivisionError,NameError和TypeError。作为异常类型打印出来的字符串是发生的内置异常的名称。对所有的内置异常来说都是正确的,但是对用户定义的异常来说不是(尽管是一个有用的约束)。标准异常的名称是内置的标识符(不是保留关键字)。


其余的行提供基于异常类型的细节和引起的原因。


错误消息的前面部分表明异常发生位置的内容,以堆栈回溯的形式。通常它包含一个列出源行的堆栈回溯;然而,它不会显示从标准输入读取的行。


内置异常列出了内置的异常以及他们的意思。


8.3 处理异常

编写处理异常的程序是可行的。看下面的例子,要求用户输入直到一个有效的整数进入,但是允许用户打断程序(使用Ctrl+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之间的语句)被执行。
  • 如果没有异常,except子句将会跳过,try语句完成。
  • 如果执行try子句时出现了异常,其余的子句将会被跳过。如果它的类型匹配except关键字后面命名的异常,except子句就会被执行,并且在try语句之后执行。
  • 如果一个异常出现但是不匹配except子句的名称,就会跳出try语句;如果没有处理被找到,它就是一个未处理的异常并且异常会停止并显示一个如上的消息。
一个try语句也许有超过一个except子句,来指定处理不同的异常。最多会有一个异常被执行。处理程序只处理出现在相应try子句中的异常,不是其他拥有相同try语句的。一个except子句可以命名多个异常作为一个元组,例如:
... except (RuntimeError, TypeError, NameError):
...     pass
如果是相同的类或者基类,在一个except子句中的一个类和一个异常是兼容的(但反过来就不行----一个列出派生类的except子句和一个基类是不兼容的)。例如,下面的代码将会以那样的顺序打印B,C,D:
class B(Exception):
    pass

class C(B):
    pass

class D(C):
    pass

for cls in [B, C, D]:
    try:
        raise cls()
    except D:
        print("D")
    except C:
        print("C")
    except B:
        print("B")
注意如果except子句是翻转的(except B优先),它将会打印B,B,B---第一个匹配到的子句将会触发。

最后一个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 OSError:
        print('cannot open', arg)
    else:
        print(arg, 'has', len(f.readlines()), 'lines')
        f.close()
使用else子句比在try子句中添加额外的代码要好,因为它避免了偶然地捕获一个由try...except语句所保护的代码引起的异常。

当一个异常出现时,都会有一个相关联的值,同样成为异常的参数。参数的存在和类型都依赖于异常的类型。

except子句可以在一个异常名称指定一个变量。变量被绑定到带有一个被储存在instance.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)
...
<class 'Exception'>
('spam', 'eggs')
('spam', 'eggs')
x = spam
y = eggs
如果一个异常有参数,他们作为为处理异常的消息的最后一行(明细)来打印。

异常处理不仅仅是处理异常如果它立刻出现在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: division by zero

8.4 抛出异常

raise语句允许程序员强制抛出一个指定的异常。例如:

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

raise唯一的参数表明了抛出的异常。这必须是一个异常实例或者一个异常类(派生自Exception)。如果传递的是一个异常类,它将暗中调用无参的构造函数来初始化:

raise ValueError  # shorthand for 'raise ValueError()'

如果你决定无论一个异常被抛出但是不想处理它,一个简单的raise语句形式能允许你重新抛出这个异常:

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

8.5 用户定义异常

程序可以命名他们自己的异常通过创建一个新的异常类。异常通常应该从异常类派生出来,不管是直接还是间接。


异常类能被定义成左其他类能做的任何事,但通常要简洁,通常只提供一些属性,这些属性允许关于错误的信息被异常的处理器提取。当创建一个能抛出几种不同错误的模块时,一个常用的做法是为那个模块定义的异常创建一个基类,和为不同错误条件创建指定异常类的子类:

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’,和标准异常的命名相似。


许多标准模块定义他们自己的异常来报告可能存在于他们定义的函数中的错误。


8.6 定义清理行为

try语句有另一个可选子句,这个字句意图顶一个在所有情况下都必须执行的清理行为。例如:

>>> try:
...     raise KeyboardInterrupt
... finally:
...     print('Goodbye, world!')
...
Goodbye, world!
KeyboardInterrupt
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
123

finally字句总是在离开try语句之前执行,不管是否有异常出现。当一个异常出现在try子句中并且没有被except子句处理(或者它出现在一个except或else子句),当finally字句执行后他将重新抛出。finally字句同样执行当任意其他的try语句的字句通过break,continue,return语句退出时。一个更复杂的例子:

>>> def divide(x, y):
...     try:
...         result = x / y
...     except ZeroDivisionError:
...         print("division by zero!")
...     else:
...         print("result is", result)
...     finally:
...         print("executing finally clause")
...
>>> divide(2, 1)
result 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 "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in divide
TypeError: unsupported operand type(s) for /: 'str' and 'str'

正如你看到的,finally字句在任何情况下都会执行。由除2个字符串抛出的TypeError没有被except字句处理因此在finally字句执行后重新抛出。


在真实应用中,finally子句对于释放额外的资源(例如文件或者网络连接)很有帮助,不管资源是否被顺利的使用。


8.7 预定义清理行为

一些对象定义需要定义的标准清理行为当对象不再需要的时候,不管是否操作成功或者失败的使用了对象。看下面的例子,尝试打开一个文件并且将它的内容输出到屏幕上:

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

这段代码的问题是,在代码执行完成后,它会在不确定的时间打开文件。在简单的脚本中这并不是一个问题,但在更大的应用中会。with语句允许对象例如文件以一种确保它们总是适当和正确的被清理的方式来使用。

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

当语句执行后,文件f总是会被关闭,即使处理行的时候遇到问题。像文件的对象,提供预定义清理行为将会在他们的文档中表明。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值