第八章 错误与异常 ——python导引编译之九

第八章 错误与异常 ——python导引编译之九

标题8. 错误与异常Errors and Exceptions

直到现在为止,错误信息还没有超过提及到的东西,但是如果你检查过这些例子,就很可能看到一些错误。至少有两类明显区别的错误:句法的错误和异常。

标题8.1. 句法错误Syntax Errors

句法错误,也以分析性错误为人所知,这也许是你在学习python中,你抱怨的最多的一个错误类别:

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

这个分析程式重复了那个有问题的行,显示出一个小箭头,该箭头指向行中最前面的那个点,正是在这一行中,有错误被检查到了。这个错误造成的原因,是因为在箭头前的那个符号所致(或者说,在那个符号检查到错误):在这个例子中,错误出现在函数print()中,在print()之前的冒号(‘:’)没有打上。文件名和行数打印出来,以便你知道在什么地方去查看来自脚本的那个输入

标题8.2. 异常Exceptions

即使一个陈述或者表达式句法上是正确的,当有执行它的意图时,也可能造成错误。在执行过程中检查到的错误就称为异常,这类错误不是无条件让执行失败:你将很快就学到在python程序中去处理这些异常。大多数异常不被程序处理,然而,在错误信息中产生的结果如下所示:

>>> 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 only concatenate str (not "int") to str

错误信息的最末行指出发生了什么。异常有不同类型,类型被打印为信息的一部分:本代码中的类型实例是除零错误,名称错误和类型错误。打印为异常类型的字符串是出现在内置异常中的异常名称。这适合于所有那些内置异常,但并不适合用户定义的异常(虽然这是一个有用的习惯)。标准异常名称是内置的标识符(不保留关键字)。
该行的其余部分提供了基于异常类型的细节和引起异常的东西。
错误信息之前的部分显示,该异常产生地方的文本,使用对追溯的形式。一般而言,这一部分含有一个列出资源行的堆回溯;然而,却不显示从标准输入读取的行。
内置异常列出了这些内置异常和它们的意义。

标题8.3.处理异常 Handling Exceptions

编写处理备选异常的程序,这是可能的。观察以下实例,他要求用户输入,一直到一个有效整数被输进为止,但允许用户打断程序(使用Control-C或者操作系统支持的无论什么指令);注意,一个用户生成的打断是用产生一个键盘打断例外来形成的。

>>> 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关键字后面命名的异常,则这个异常从句被执行,再接之,在try程序之后,该执行继续。
如果一个异常,它并不匹配在异常从句中命名的那个异常,却产生了,那么它就会在外部的try程序通过;如果没有处理者发现,那它就是一个未经处理的异常,执行由此就停下来,用一个如上所示的信息告知。

一个try程序也许不止一个异常从句,要给这些不同的异常指派特定的处理者。最多,一个处理者被执行。处理者仅仅处理产生在相应try从句中的异常,而非同样try陈述中的另外的处理者。一个异常从句可以命名多种异常,把这些异常放在括起来的元组之中,例如:

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

在一个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从句被触发了。
最后一个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 clause,这个从句,当呈现之时,必须遵从所有的异常从句。如果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()

否则从句的使用比给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. 抛出异常Raising Exceptions

这个抛出陈述允许程序员强制一个指定的异常产生。例如:

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

‘抛出’这个唯一的参数指示了该异常产生出来。这必须或者是一个异常特例,或者是一个异常类(从异常导出的类)。如果一个异常类通过,它将是调用其无参数的构造器而明确获得了示例:

>>> raise ValueError  #缩写‘raise ValueError()'而成

如果你需要判定一个异常是否抛出,但有不想执行它,那么一个更为简单的raise形式是允许你重复使用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. 异常链Exception Chaining

这个抛出陈述允许一个可选的from,这个from能够组成一个异常链,通过设置该抛出异常的函数的属性,这个函数是_cause_。例如:

>>> raise RuntimeError from OSError

当你正在传送异常的时候,这可能是有用的。例如:

>>> def func():
...    raise IOError
...
>>> try:
...     func()
... except IOError as exc:
...     raise RuntimeError('Failed to open database')from exc
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "<stdin>", line 2, in func
OSError

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
RuntimeError: Failed to open database

尾随from的表达式必须或者是一个异常,或者是一个无None。当一个异常在异常处理者内部或者在最后部分抛出,异常链就自动出现了。异常链可以使用from None指令而不产生作用:

>>> try:
...     open('database.sqlite')
... except IOError:
...     raise RuntimeError from None
...
Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
RuntimeError

标题8.6.用户定义的异常 User-defined Exceptions

程序可以命名自身的异常,通过创建一个新的异常类(更多有关python类的东西,请参看类classes)。异常会典型地从异常类中导出,或者是直接的,或者是简接的。
可以定义一个异常类,这个异常类可以做任意一个其它类所做的任何事情,但是,通常为保持简洁,仅仅给出一类属性,这类属性允许有关错误的信息可被处理者为该异常抽象出来。当创建一个可以抛出几个有区别的错误这样的模块时,一个通常的实践就是用这个模块为异常创建一个基础类,再去为不同的错误条件创建特定异常类的一个子类:

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,类似于标准异常的名称。
许多标准模块定义了其自身的异常去报告错误,在它们定义的函数中出现的错误。对于类classes的更多信息,请看类那一章中给出的内容。

标题8.7.定义清除行为 Defining Clean-up Actions

那个try陈述有另一个可选的从句,该从句意图定义清除行为,也就是在所有情况下必须执行的行为。例如:

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

如果一个finally从句呈现出来,这个从句将执行try陈述完成前的最后一个任务。这个finally从句,无论那个try陈述是否产生一个异常,它都会运行。以下诸点是在一个异常产生时,讨论更复杂的情形:
如果一个异常在整个try从句执行期间发生,该异常也许被一个except从句处理。如果该异常并不被一个except从句处理,它在finally从句执行之后,会来一个再抛出re-raise。
一个异常可以在一个except或者else 从句执行期间发生。再一点,这个异常在finally从句执行之后被再抛出。
如果try陈述到达一个break陈述,或者continue陈述,或者返回陈述return,那个finally从句将恰好在这三种陈述前执行。
如果一个finally从句包括了一个返回陈述,其返回值将是来自finally从句的返回陈述,不是来自try从句的返回值。
例如:

>>> def bool_return():
...     try:
...         return True
...     finally:
...         return False
...
>>> bool_return()
False

一个更复杂的例子:

>>> def divide(x, y):
...     try:
...         result = x / y
...     except ZeroDivisionError:
...         print("division by zero!")
...     else:
...         print("result is", result)
...     finally:
...         print("exceuting finally clause")
...
>>> divide(2, 1)
result is 2.0
exceuting finally clause
>>> divide(2, 0)
division by zero!
exceuting finally clause
>>> divide("2", "1")
exceuting 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从句在任意事件中都被执行。那个TypeError是因用两个字符串相除而产生的,该TypeError不被except从句处理,因此在finally从句执行后出现再抛出re-raise。
在实际世界的运用中,finally从句对于释放外部资源很有用(例如文件或者网络连接),无论这些资源的运用是否成功。

标题8.8.预定义的清除行为 Predefined Clean-up Actions

有一些对象,当该对象不再需要的时候,定义标准清除行为就成为责任,而无论这种使用对象的操作是成功还是不成功。请看以下实例,这个实例试图打开一个文件并在屏幕上打印出文件内容。

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

伴随这个代码产生的问题是,在这个代码的部分已经做完执行之后,它留下了一个打开的文件,而这个文件的时间量则是未判定的。在简单的脚本中,这似乎不是问题,但对于更大的运用,很可能就是一个问题。那个with陈述承诺类似这样在某个方式中被使用的文件,总是可以快速而且正确地清除掉。

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

在这个陈述被处理之后,文件f总是处在关闭状态,即使当处理这些行的时候,要面对一个问题。类似文件这样的对象,提供预定义的清除行为将在其文件中指明这一点。

猜你喜欢

转载自blog.csdn.net/weixin_41670255/article/details/109165324
今日推荐