[译]The Python Tutorial#Errors and Exceptions

到现在为止都没有过多介绍错误信息,但是已经在一些示例中使用过错误信息。Python至少有两种类型的错误:语法错误以及异常

8.1 Syntax Errors

语法错误,也称解析错误,是Python初学者经常抱怨的问题。

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

Python解析器重复打印错误行,并且展示一个小箭头,箭头指向错误行中错误最早被侦测到的位置。错误由箭头前面的语句导致(至少侦测到是这样的):以上示例中,错误在print()函数被侦测到,因为在它之前的冒号:缺失了。文件名和行号也被打印出来,当程序来自脚本时可以方便定位问题。

8.2 Exceptions

即使语句或者表达式在语法上是正确的,但是在执行的时候也可能会发生错误。在执行期间侦测到的问题叫做异常,异常不是绝对的致命错误:接下来会介绍如何在Python中处理异常。然而大多数异常都不会被程序处理,它们最终会成为错误信息,展示如下:

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

最后一行错误信息指出发生了什么。异常有不同类型,类型也作为错误信息的一部分打印出来:以上示例中的类型有除零异常ZeroDivisionError, 名字异常NameError 以及类型异常TypeError。作为异常类型打印的字符串是built-in异常的名字。对于所有built-in异常来说都是如此,尽管这个约定很有用,但是并不是所有用户自定义异常都会遵守。标准异常名字是built-in标识符(不是保留字)。

剩余行基于异常类型展示了详细的信息以及异常发生的原因。

前面部分错误信息以堆栈回溯的方式,展示异常发生的上下文信息。通常堆栈回溯中列出了源代码行;然而,当程序是从标准输入中读取时,不会展示行。

Built-in异常列举了所有built-in异常以及它们的意义。

8.3 Handling Exceptions

Python程序允许处理指定异常。以下程序要求用户循环输入,直到输入为有效整数停止,但是也可以中断程序(使用Control-C或者其他操作系统支持的手段);注意用户诱发的中断会抛出KeyboardInterrupt异常。

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

try语句的执行顺序为:

  • 首先,执行在tryexcept之间的try子句
  • 如果没有异常发生,跳过except子句try语句执行完毕
  • 如果try子句执行期间有异常发生,该子句内剩余语句被跳过。接下来如果抛出的异常类型可以与关键字except后的异常匹配,那么except子句执行,接着继续执行try语句后的语句。
  • 如果异常发生但是没有匹配到except后的异常,那么异常抛到外层try语句;如果异常得不到处理,该异常成为未处理异常,导致程序终止并且像以上示例一样打印信息。

一个try语句可以有多个except子句,为不同的异常指定处理方法。至多只有一个except子句会被执行。处理程序只会处理发生在对应try子句中的异常,而不会处理发生在同一try语句中其他处理程序中的异常。一个except子句可以使用带括号的元组列出多个异常,就像这样:

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

except子句中的类可以匹配类型是其子类或者它自己类型的异常(但反过来不行,except子句中的子类不会匹配父类异常)。例如以下代码会依次打印B, C, D:

  1. class B(Exception):
  2. pass
  3. class C(B):
  4. pass
  5. class D(C):
  6. pass
  7. for cls in [B, C, D]:
  8. try:
  9. raise cls()
  10. except D:
  11. print("D")
  12. except C:
  13. print("C")
  14. except B:
  15. print("B")

注意以上的except子句若反过来写(except B在前),那么将会打印B, B, B ——第一个except子句的匹配被触发。

最后一个except子句可以省略异常名字作为通配符。因为这样做会隐藏其他的真实程序错误,所有要谨慎使用。也可以用来打印错误信息并且重新抛出(允许调用者处理异常):

  1. import sys
  2. try:
  3. f = open('myfile.txt')
  4. s = f.readline()
  5. i = int(s.strip())
  6. except OSError as err:
  7. print("OS error: {0}".format(err))
  8. except ValueError:
  9. print("Could not convert data to an integer.")
  10. except:
  11. print("Unexpected error:", sys.exc_info()[0])
  12. raise

try...except语句有一个可选的else子句,该子句只能出现在所有except子句之后。若要求try子句没有抛出异常时必须执行一段代码,使用else子句很有用(译注:else子句会被return, break, continue跳过)。例如:

  1. for arg in sys.argv[1:]:
  2. try:
  3. f = open(arg, 'r')
  4. except OSError:
  5. print('cannot open', arg)
  6. else:
  7. print(arg, 'has', len(f.readlines()), 'lines')
  8. f.close()

使用 else 子句比在 try 子句中附加代码要好,因为这样可以避免 try ... except 意外的捕获的本不属于它们保护的那些代码抛出的异常。

异常发生时,可以携带与之关联的值,也称作异常参数。异常参数是否可以存在以及其类型取决于异常类型。

except子句可以在异常名字后指定变量。该变量绑定到异常实例,异常参数存储在instance.args属性中。方便起见,异常实例定义了__str__()以便异常参数可以直接打印,而不是使用.args引用。也可以首先实例化异常,并在抛出它之前加入任何想要的参数:

  1. >>> try:
  2. ... raise Exception('spam', 'eggs')
  3. ... except Exception as inst:
  4. ... print(type(inst)) # the exception instance
  5. ... print(inst.args) # arguments stored in .args
  6. ... print(inst) # __str__ allows args to be printed directly,
  7. ... # but may be overridden in exception subclasses
  8. ... x, y = inst.args # unpack args
  9. ... print('x =', x)
  10. ... print('y =', y)
  11. ...
  12. <class 'Exception'>
  13. ('spam', 'eggs')
  14. ('spam', 'eggs')
  15. x = spam
  16. y = eggs

对于未处理异常来说,如果它有参数,那么参数将在错误信息的后面部分(详细信息部分)打印。

异常处理程序不仅仅会处理即时发生在try子句中的异常,还会处理try子句中直接或者间接调用的函数中发生的异常。例如:

  1. >>> def this_fails():
  2. ... x = 1/0
  3. ...
  4. >>> try:
  5. ... this_fails()
  6. ... except ZeroDivisionError as err:
  7. ... print('Handling run-time error:', err)
  8. ...
  9. Handling run-time error: division by zero

8.4 Raising Exceptions

raise语句允许程序员强制发生异常。例如:

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

raise的唯一参数指定要抛出的异常。参数必须是异常实例或者异常类(继承自Exception类的子类)。如果参数是异常类型,会隐式使用无参方式调用异常的构造器初始化一个异常实例:

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

如果需要捕获异常但是不处理,一种更简单形式的raise语句允许重新抛出这个异常:

  1. >>> try:
  2. ... raise NameError('HiThere')
  3. ... except NameError:
  4. ... print('An exception flew by!')
  5. ... raise
  6. ...
  7. An exception flew by!
  8. Traceback (most recent call last):
  9. File "<stdin>", line 2, in <module>
  10. NameError: HiThere

8.5 User-defined Exception

程序中可以通过创建新异常类的方式提出自己的异常(参见Classes获取Python类的更多信息)。异常必须直接或者间接继承自Exception类。

自定义异常类拥有其他类的功能,但通常需要保持其简洁性,只提供几个供异常处理程序提取错误信息的属性。需要创建一个抛出若干不同错误的模块时,比较好的实践是,为定义在这个模块中的异常创建父类,由子类创建对应不同错误的具体异常:

  1. class Error(Exception):
  2. """Base class for exceptions in this module."""
  3. pass
  4. class InputError(Error):
  5. """Exception raised for errors in the input.
  6. Attributes:
  7. expression -- input expression in which the error occurred
  8. message -- explanation of the error
  9. """
  10. def __init__(self, expression, message):
  11. self.expression = expression
  12. self.message = message
  13. class TransitionError(Error):
  14. """Raised when an operation attempts a state transition that's not
  15. allowed.
  16. Attributes:
  17. previous -- state at beginning of transition
  18. next -- attempted new state
  19. message -- explanation of why the specific transition is not allowed
  20. """
  21. def __init__(self, previous, next, message):
  22. self.previous = previous
  23. self.next = next
  24. self.message = message

与标准异常类类似,大多数异常的名字都以"Error"结尾。

许多标准模块都定义了自己的异常,这些异常对应模块中定义的函数中可能发生的错误。更多信息参考Classes

8.6 Defining Clean-up Actions

try语句有可选的在任何情况下都会执行的子句,可用于定义清理动作。例如:

  1. >>> try:
  2. ... raise KeyboardInterrupt
  3. ... finally:
  4. ... print('Goodbye, world!')
  5. ...
  6. Goodbye, world!
  7. KeyboardInterrupt
  8. Traceback (most recent call last):
  9. File "<stdin>", line 2, in <module>

无论是否有异常发生,finally子句在离开try语句之前总是会执行。当try子句中有异常发生并且没有被except子句处理(或者异常发生在except子句或else子句),finally子句执行之后,这些异常会重新抛出。当try语句的其他子句通过break, continue或者return语句离开时,finally子句也会执行。以下是较为复杂的示例:

  1. >>> def divide(x, y):
  2. ... try:
  3. ... result = x / y
  4. ... except ZeroDivisionError:
  5. ... print("division by zero!")
  6. ... else:
  7. ... print("result is", result)
  8. ... finally:
  9. ... print("executing finally clause")
  10. ...
  11. >>> divide(2, 1)
  12. result is 2.0
  13. executing finally clause
  14. >>> divide(2, 0)
  15. division by zero!
  16. executing finally clause
  17. >>> divide("2", "1")
  18. executing finally clause
  19. Traceback (most recent call last):
  20. File "<stdin>", line 1, in <module>
  21. File "<stdin>", line 3, in divide
  22. TypeError: unsupported operand type(s) for /: 'str' and 'str'

正如所见的那样,finally子句在任何情况下都会执行。两个字符串相除产生的TypeError异常没有被except子句处理,因此在finally子句执行完毕之后被重新抛出。

在实践应用中,finally子句用来释放外部资源(比如文件和网络连接),不论资源的使用是否成功。

8.7 Predefined Clean-up Actions

一些对象定义了标准的清理动作,当不再需要这些对象时,会执行清理动作,而不论使用对象的操作是否成功。以下示例读取文件并打印内容到显示器:

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

这段代码的问题是:当代码执行完毕后,文件会保留打开状态一段不确定的时间。这在简单的脚本中不是什么大问题,但是在大型的应用程序中会出问题。with语句使得像文件一样的对象可以被立即准确回收。

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

语句执行后,即使在执行代码时遇到问题,文件f总是会被关闭。像文件一样提供预定义清理动作的对象会在其说明文档中指示这点。

[译]The Python Tutorial#8. Errors and Exceptions的更多相关文章

  1. 《The Python Tutorial》——Errors and Exceptions 阅读笔记

    Errors and Exceptions 官方文档:https://docs.python.org/3.5/tutorial/errors.html python中所有的异常都继承自BaseExce ...

  2. [译]The Python Tutorial#4. More Control Flow Tools

    [译]The Python Tutorial#More Control Flow Tools 除了刚才介绍的while语句之外,Python也从其他语言借鉴了其他流程控制语句,并做了相应改变. 4.1 ...

  3. [译]The Python Tutorial#11. Brief Tour of the Standard Library — Part II

    [译]The Python Tutorial#Brief Tour of the Standard Library - Part II 第二部分介绍更多满足专业编程需求的高级模块,这些模块在小型脚本中 ...

  4. [译]The Python Tutorial#10. Brief Tour of the Standard Library

    [译]The Python Tutorial#Brief Tour of the Standard Library 10.1 Operating System Interface os模块为与操作系统 ...

  5. [译]The Python Tutorial#12. Virtual Environments and Packages

    [译]The Python Tutorial#Virtual Environments and Packages 12.1 Introduction Python应用经常使用不属于标准库的包和模块.应 ...

  6. [译]The Python Tutorial#2. Using the Python Interpreter

    [译]The Python Tutorial#Using the Python Interpreter 2.1 Invoking the Interpreter Python解释器通常安装在目标机器的 ...

  7. [译]The Python Tutorial#1. Whetting Your Appetite

    [译]The Python Tutorial#Whetting Your Appetite 1. Whetting Your Appetite 如果你需要使用计算机做很多工作,最终会发现很多任务需要自 ...

  8. [译]The Python Tutorial#7. Input and Output

    [译]The Python Tutorial#Input and Output Python中有多种展示程序输出的方式:数据可以以人类可读的方式打印出来,也可以输出到文件中以后使用.本章节将会详细讨论 ...

  9. [译]The Python Tutorial#5. Data Structures

    [译]The Python Tutorial#Data Structures 5.1 Data Structures 本章节详细介绍之前介绍过的一些内容,并且也会介绍一些新的内容. 5.1 More ...

随机推荐

  1. 实训H5+CSS 太极图

    大概就是上面这个样子 我们准备 两个半圆,两个大圆,两个小圆,然后稍微的进行覆盖就行~ <!doctype html> <html> <head> <meta ...

  2. 063 Unique Paths II 不同路径 II

    这是“不同路径” 的进阶问题:现在考虑网格中有障碍物.那样将会有多少条不同的路径从左上角到右下角?网格中的障碍物和空位置分别用 1 和 0 来表示.例如,如下所示在 3x3 的网格中有一个障碍物.[  ...

  3. 使用cp命令拷贝目录下指定文件外的其他文件

    shopt -s extglob cp test/!(abc*) test2/ cp test目录下除了以abc开头的其他文件 如果是除去多个文件的话使用   !(a|b)   ;   注意不要多加空 ...

  4. asp.net 多语言 在IIS7.5发布出现找不到资源文件

    我也遇到这个问题,纠结了半天, 最后把资源文件的属性改为:内容 就可以了. 见:http://q.cnblogs.com/q/60443/

  5. Shell分割字符得到数组

    #!/bin/bash p=$(hadoop fs -ls /tgl/data |awk '{print $8}') #要将$a分割开,先存储旧的分隔符 OLD_IFS="$IFS" ...

  6. c#基础 base和this的区别,在继承上面

    base public Person(string name, int age, char gender) { this.Name = name; this.Age = age; this.Gende ...

  7. iOS 面试常问之多线程

    本片围绕多线程全面展开叙述. 1.为什么要有多线程/多线程是用来干什么的? 2.多线程是什么? 3.如何创建多线程? 4.多线程在哪些情况下会使用/多线程使用场景? 5.三种多线程的优缺点? 6.线程 ...

  8. cocos2dx贝塞尔曲线--使用PS辅助规划动作路径

    bool HelloWorld::init() { ////////////////////////////// // 1. super init first if ( !Layer::init() ...

  9. 集合(List、Set)

    第19天 集合 第1章 List接口 我们掌握了Collection接口的使用后,再来看看Collection接口中的子类,他们都具备那些特性呢? 接下来,我们一起学习Collection中的常用几个 ...

  10. Java面向对象(继承、抽象类)

    面向对象 今日内容介绍 u 继承 u 抽象类 第1章 继承 1.1 继承的概念 在现实生活中,继承一般指的是子女继承父辈的财产.在程序中,继承描述的是事物之间的所属关系,通过继承可以使多种事物之间形成 ...