Python3.7官方向导翻译之Python错误和异常

到目前为止,错误消息还没有被提及,但如果你尝试过你可能见过的一些例子。 有(至少)两种可区分的错误:语法错误和异常。

语法错误syntax errors

语法错误,也称为解析错误parsing errors,可能是您在学习Python时得到的最常见的抱怨:

while True print ('Hello world')
  File "<ipython-input-1-1201eae45e42>", line 1
    while True print ('Hello world')
                   ^
SyntaxError: invalid syntax

解析器重复有问题的行并显示一个指向检测到错误的行中最早点的“箭头”。 错误是由箭头前面的标记(或至少在其处检测到)引起的:在此示例中,由于在它之前缺少冒号(’:’),所以在函数print()处检测到错误。 文件名和行号会打印出来,以便在输入来自脚本时知道在哪里查看。

异常

即使语句或表达式在语法上是正确的,但在尝试执行它时可能会导致错误。 执行过程中检测到的错误称为异常,并不是无条件致命的:您将很快学会如何在Python程序中处理它们。 然而,大多数异常不是由程序处理的,并且会导致如下所示的错误消息:

10 * (1/0)
---------------------------------------------------------------------------

ZeroDivisionError                         Traceback (most recent call last)

<ipython-input-2-0b280f36835c> in <module>()
----> 1 10 * (1/0)


ZeroDivisionError: division by zero
4 + spam*3
---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

<ipython-input-3-c98bb92cdcac> in <module>()
----> 1 4 + spam*3


NameError: name 'spam' is not defined
'2' + 2
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-4-d2b23a1db757> in <module>()
----> 1 '2' + 2


TypeError: Can't convert 'int' object to str implicitly

错误消息的最后一行表示发生了什么。 异常有不同的类型,并且类型被打印为消息的一部分:示例中的类型是ZeroDivisionError,NameError和TypeError。 作为异常类型打印的字符串是引起的内置异常的名称。 对于所有内置的异常情况都是如此,但对于用户定义的异常情况不必如此(尽管这是一个有用的约定)。 标准异常名称是内置标识符(不是保留字)。

该行的其余部分根据异常的类型以及导致它的原因提供了详细信息。

错误消息的前面部分以堆栈回溯的形式显示发生异常的上下文context。 通常它包含一个栈回溯列表源代码行; 但是,它不会显示从标准输入读取的行。

详细的内置异常和他们的含义可以参考内置异常部分

异常处理

编写处理选定异常的程序是可能的。 看下面的例子,它会要求用户输入,直到输入一个有效的整数,但允许用户中断程序(使用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语句的工作原理如下。
1. 首先,try子句(try和except关键字之间的语句)被执行。
2. 如果没有发生异常,则跳过except子句并结束try语句的执行。
3. 如果在执行try子句期间发生异常,则跳过子句的其余部分。 然后,如果它的类型匹配以except关键字命名的异常,则会执行except子句,然后在try语句之后继续执行。
4. 如果发生的异常与except子句中指定的异常不匹配,则会传递给外部try语句; 如果没有找到处理程序,则它是一个未处理的异常,并执行停止并显示如上所示的消息。

try语句可能有多个except子句,用于为不同的异常指定处理程序。 最多只有一个处理程序将被执行。 处理程序只处理发生在相应try子句中的异常,而不处理相同try语句的其他处理程序。 except子句可以将多个异常命名为括号化的元组,例如:

excapt (RuntimeError, TypeError, NameError):
    pass

如果except子句中的类是相同的类或其基类(但不是反其道而行之 - 列出一个派生类的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 子句。

最后一个except子句可以省略exception name(s),作为通配符。 请谨慎使用此功能,因为以这种方式很容易掩盖真正的编程错误! 它也可以用来打印错误消息,然后重新引发异常(允许调用者也处理异常):

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子句,当存在时,它必须遵循除了子句之外的所有子句。 如果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语句的代码引发的意外的异常。

当异常发生时,可能有一个关联的值associated value,也称为异常的参数。 参数的存在和类型取决于异常类型。

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

如果一个异常有参数,它们将作为未处理异常消息的最后一部分(’detail’)打印出来。

异常处理程序不仅仅处理发生在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

引发异常raise exception

raise语句允许程序员强制执行指定的异常。 例如

raise  NameError('HiThere')
---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

<ipython-input-4-544f61831305> in <module>()
----> 1 raise  NameError('HiThere')


NameError: HiThere

raise的唯一参数表明了要引起的异常。 这必须是异常实例或异常类(从Exception派生的类)。 如果传递一个异常类,它将通过调用它的没有参数的构造函数被隐式实例化:

raise.ValueError # shorthand for 'raise ValueError()'

如果您需要确定是否引发了异常但无意处理异常,则可以使用更简单的raise语句来重新引发异常:

try:
    raise NameError('HiThere')
except NameError:
    print('An except flew by!')
    raise
An except flew by!



---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

<ipython-input-6-7204dc067e2b> in <module>()
      1 try:
----> 2     raise NameError('HiThere')
      3 except NameError:
      4     print('An except flew by!')
      5     raise


NameError: HiThere

用户定义的异常

程序可以通过创建一个新的异常类来命名它们自己的异常(更多关于Python类的参见Classes)。 异常通常应直接或间接地从Exception类派生。

可以定义异常类,它可以执行任何其他类可以执行的任何操作,但通常很简单,通常只提供一些属性,以便为异常提取有关错误的信息。 创建可引发多个不同错误的模块时,通常的做法是为该模块定义的异常创建基类,并创建用于为不同错误条件创建特定异常类的子类

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 pccurred
        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”结尾的名字来定义,类似于标准异常的命名

许多标准模块定义它们自己的异常,以报告可能在其定义的功能中发生的错误。 关于类的更多信息在类章节中介绍。

定义清理行为 defining clean-up actions

try 语句还有另一个可选的字句,它用来定义在所有情况circumstances下都必须要执行的清理行为

try:
    raise KeyboardInterrupt
finally:
    print('Goodbye, world')
Goodbye, world



---------------------------------------------------------------------------

KeyboardInterrupt                         Traceback (most recent call last)

<ipython-input-13-97d18bce1156> in <module>()
      1 try:
----> 2     raise KeyboardInterrupt
      3 finally:
      4     print('Goodbye, world')


KeyboardInterrupt: 

finally子句总是在离开try语句之前执行,无论是否发生异常。 当try子句中发生异常并且没有被except子句处理(或者它发生在except或else子句中)时,它会在finally子句执行后重新引发。 当try语句中有任何通过break,continue或return语句表示的其他子句时,finally子句也会在他们之后(on the way out)执行。 一个更复杂的例子:

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



---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-17-0fe65f250ff1> in <module>()
----> 1 divide('2', '1')


<ipython-input-15-1485fbbc6e8b> in divide(x, y)
      1 def divide(x, y):
      2     try:
----> 3         result = x/y
      4     except ZeroDivisionError:
      5         print('division by zero!')


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=' ')

语句执行后,即使在处理行时遇到问题,文件f也始终关闭。 与文件一样,提供预定义清理操作的对象将在其文档中指明。

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页