Skip to content

Python 学习笔记 8. 错误和异常

🏷️ Python Python 学习笔记

两种错误:语法错误异常

8.1 语法错误

语法错误 又称 解析错误

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

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

错误信息的最后一行告诉我们程序遇到了什么类型的错误。

异常有不同的类型,而其类型名称将会作为错误信息的一部分中打印出来:上述示例中的异常类型依次是: ZeroDivisionErrorNameErrorTypeError

8.3. 处理异常

使用 try 语句。

类似于 C# 中的 try ... catch 关键字。

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

try 语句的工作原理:

  • 首先,执行 try 子句(tryexcept 关键字之间的(多行)语句)。

  • 如果没有异常发生,则跳过 except 子句 并完成 try 语句的执行。

  • 如果在执行 try 子句时发生了异常,则跳过该子句中剩下的部分。然后,如果异常的类型和 except 关键字后面的异常匹配,则执行 except 子句,然后继续执行 try 语句之后的代码。

  • 如果发生的异常和 except 子句中指定的异常不匹配,则将其传递到外部的 try 语句中;如果没有找到处理程序,则它是一个 未处理异常 ,执行将停止并显示如上所示的消息。

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

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

最后的 except 子句可以省略异常名,以用作通配符。
可用于打印错误消息,然后重新引发异常。

python
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 子句后面。

这个功能是 C# 中没有的,其类似于在 try 部分的最后执行这部分代码,但还是有些区别的。最大区别在于若这部分代码发生异常,负责捕获这部分异常的代码是不一样的。放在 else 中时发生异常,是会被外部的 except 捕捉,而不是当前 try 块中的 except

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

except 子句可以在异常名称后面指定一个变量(异常参数)。这个变量和一个异常实例绑定,它的参数存储在 instance.args 中。

python
>>> 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

如果异常有参数,则它们将作为未处理异常的消息的最后一部分('详细信息')打印。

python
>>> 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 语句允许程序员强制发生指定的异常。

类似于 C# 中的 throw 关键字。

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

如果你需要确定是否引发了异常但不打算处理它,则可以使用更简单的 raise 语句形式重新引发异常。

python
>>> 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. 用户自定义异常

程序可以通过创建新的异常类来命名它们自己的异常。

和 C# 中的自定义异常类似。

python
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 语句有另一个可选子句,用于定义必须在所有情况下执行的清理操作。

类似于 C# 中的 finally 关键字。

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

如果存在 finally 子句,则 finally 子句将作为 try 语句结束前的最后一项任务被执行。
finally 子句不论 try 语句是否产生了异常都会被执行。

以下几点讨论了当异常发生时一些更复杂的情况:

  • 如果在执行 try 子句期间发生了异常,该异常可由一个 except 子句进行处理。如果异常没有被 except 子句所处理,则该异常会在 finally 子句执行之后被重新引发。

  • 异常也可能在 exceptelse 子句执行期间发生。同样地,该异常会在 finally 子句执行之后被重新引发。

  • 如果在执行 try 语句时遇到一个 break, continuereturn 语句,则 finally 子句将在执行 break, continuereturn 语句之前被执行。

  • 如果 finally 子句中包含一个 return 语句,则 finally 子句的 return 语句将在执行 try 子句的 return 语句之前取代后者被执行。

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

8.7. 预定义的清理操作

使用 with 语句来确保对象得到及时和正确的清理。

类似于在 C# 中的 using 关键字。

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

执行完语句后,即使在处理行时遇到问题,文件 f 也始终会被关闭。