Python 中 (至少) 有两种错误: 语法错误 (syntax errors) 和异常(exceptions).
语法错误
语法错误又称作解析错误:
- #!python
- >>> while True print('Hello world')
- File "<stdin>", line 1
- while True print('Hello world')
- ^
- SyntaxError: invalid syntax
语法分析器指出错误行, 并且在检测到错误的位置前面显示 ^.
异常
即使语句或表达式在语法上是正确的, 执行时也可能会引发错误. 运行期检测到的错误称为异常
- #!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
错误信息的最后一行指出发生了什么错误. 异常也有不同的类型, 异常类型做为错误信息的一部分显示出来: 示例中的异常分别为 https://docs.python.org/3/library/exceptions.html#ZeroDivisionError , https://docs.python.org/3/library/exceptions.html#NameError 和 https://docs.python.org/3/library/exceptions.html#TypeError . 打印错误信息时, 异常的类型作为异常的内置名显示. 对于所有的内置异常都是如此, 不过用户自定义异常就不一定了(尽管这是很有用的约定). 标准异常名是内置的标识符(非保留关键字).
这行后一部分是关于该异常类型的详细说明, 这意味着它的内容依赖于异常类型.
错误信息的前半部分以堆栈的形式列出异常发生的位置. 通常在堆栈中列出了源代码行, 然而, 来自标准输入的源码不会显示出来.
内置的异常 https://docs.python.org/3/library/exceptions.html#bltin-exceptions 列出了内置异常和它们的含义.
异常处理
例子: 要求用户输入直到输入合法的整数为止, 但允许用户中断这个程序(使用 Control-C 或系统支持的任何方法). 注意: 用户产生的中断会引发 https://docs.python.org/3/library/exceptions.html#KeyboardInterrupt 异常.
- #!python
- >>> while True:
- ... try:
- ... x = int(input("Please enter a number:"))
- ... break
- ... except ValueError:
- ... print("Oops! That was no valid number. Try again...")
- ...
https://docs.python.org/3/reference/compound_stmts.html#try 语句按如下方式工作.
首先, 执行 try 和 except 之间的内容
如果没有异常发生, 忽略 except 语句.
如果在 try 子句执行过程中发生了异常, 那么该子句其余的部分就会忽略. 如果异常匹配于 * 后面指定的异常类型, 就执行对应的 except 子句. 然后继续执行 try 语句之后的代码.
如果发生了一个异常, 在 except 子句中没有与之匹配的分支, 它就会传递到上一级 try 语句中. 如果最终仍找不到对应的处理语句, 它就成为未处理异常, 终止程序运行, 显示提示信息.
try 语句可能包含多个 except 子句, 分别指定处理不同的异常. 至多执行一个分支. 异常处理程序只会处理对应的 try 子句中发生的异常, 在同一 try 语句中, 其他子句中发生的异常则不作处理. except 子句可以在元组中列出多个异常的名字, 例如:
- #!python
- ... except (RuntimeError, TypeError, NameError):
- ... pass
异常匹配如果 except 子句中的类相同的类或其基类(反之如果是其子类则不行). 例如, 以下代码将按此顺序打印 B,C,D:
- #!python
- 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 B 在先, 将打印 B,B,B.
最后 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 子句之后. 当 try 语句没有抛出异常时, 需要执行一些代码, 可以使用这个子句. 例如:
- #!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()
使用 else 子句比在 try 子句中附加代码要好, 因为这样可以避免 try ... except 意外的截获本来不属于它们的那些代码抛出的异常.
发生异常时, 可能会有相关值, 作为异常的参数存在. 这个参数是否存在, 是什么类型, 依赖于异常的类型.
在异常名 (元组) 之后, 也可以为 except 子句指定一个变量. 这个变量绑定于异常实例, 它存储在 instance.args 参数中. 为了方便起见, 异常实例定义了 str() https://docs.python.org/3/reference/datamodel.html#object.__str__ , 这样就可以直接访问过打印参数而不必引用. 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
对于那些未处理的异常, 如果它们带有参数, 那么就会被作为异常信息的最后部分 ("详情") 打印出来.
异常处理器不仅仅处理那些在 try 子句中立刻发生的异常, 也会处理那些 try 子句中调用的函数内部发生的异常. 例如:
- #!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
抛出异常
https://docs.python.org/3/reference/simple_stmts.html#raise 语句可强行抛出指定的异常. 例如:
- #!python
- >>> raise NameError('HiThere')
- Traceback (most recent call last):
- File "<stdin>", line 1, in <module>
- NameError: HiThere
要抛出的异常由 raise 的唯一参数标识. 它必需是异常实例或异常类(继承自 https://docs.python.org/3/library/exceptions.html#Exception 的类). 如果传递异常类, 它将通过调用它的没有参数的构造函数而隐式实例化:
- #!python
- raise ValueError # shorthand for 'raise ValueError()'
如果你想知道异常是否抛出, 但不想处理它, 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
用户自定义异常
直接或间接的继承 https://docs.python.org/3/library/exceptions.html#Exception 可以自定义异常.
异常类中可以定义任何其它类中可以定义的东西, 但是通常为了保持简单, 只在其中加入几个属性信息, 以供异常处理句柄提取. 如果新创建的模块中需要抛出几种不同的错误时, 通常的作法是为该模块定义异常基类, 然后针对不同的错误类型派生出对应的异常子类:
- #!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" 结尾.
很多标准模块中都定义了自己的异常, 以报告在他们所定义的函数中可能发生的错误. 关于类的进一步信息请参见 Classes https://docs.python.org/3/tutorial/classes.html#tut-classes .
定义清理行为
https://docs.python.org/3/reference/compound_stmts.html#try 语句有可选的子句定义在任何情况下都一定要执行的清理操作. 例如:
- #!python
- >>> try:
- ... raise KeyboardInterrupt
- ... finally:
- ... print('Goodbye, world!')
- ...
- Goodbye, world!
- KeyboardInterrupt
- Traceback (most recent call last):
- File "<stdin>", line 2, in <module>
不管有没有发生异常, finally 子句在程序离开 try 前都会被执行. 当语句中发生了未捕获的异常(或者它发生在 except 或 else 子句中), 在执行完 finally 子句后它会被重新抛出. try 语句经由 break,continue 或 return 语句退出也会执行 finally 子句.
- #!python
- >>> 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 子句多用于释放外部资源(文件或网络连接之类的).
预定义清理行为
有些对象定义了标准的清理行为, 无论对象操作是否成功, 不再需要该对象的时候就会起作用. 以下示例尝试打开文件并把内容输出到屏幕.
- #!python
- for line in open("myfile.txt"):
- print(line, end="")
这段代码的问题在于在代码执行完后没有立即关闭打开的文件. 简单的脚本里没什么, 但是大型应用程序就会出问题. https://docs.python.org/3/reference/compound_stmts.html#with 语句使得文件之类的对象能及时准确地进行清理.
- #!python
- with open("myfile.txt") as f:
- for line in f:
- print(line, end="")
语句执行后, 文件 f 总会被关闭, 即使是在处理文件行时出错也一样. 其它对象是否提供了预定义的清理行为要参考相关文档.
参考资料
讨论 qq 群 144081101 591302926 567351477 钉钉免费群 21745728
本文最新版本地址 https://china-testing.github.io/python3_crash1.html
本文涉及的 python 测试开发库 https://github.com/china-testing/python-api-tesing 谢谢点赞!
本文相关海量书籍下载 https://github.com/china-testing/python-api-tesing/blob/master/books.md
本文源码地址 https://github.com/china-testing/python-api-tesing/tree/master/python_crash_tutorial
异常处理实例: 正则表达式及拼音排序
有某群的某段聊天记录 https://github.com/china-testing/python-api-tesing/blob/master/weeks/2018_06_01/test.txt
现在要求输出排序的 qq 名, 结果类似如下:
- #!python
- [..., '本草隐士', 'jerryyu', '可怜的樱桃树', '叻风云', '欧阳 - 深圳白芒', ...]
- #!python
- #!/usr/bin/python3
- # -*- coding: utf-8 -*-
- # Author: xurongzhong@126.com wechat:pythontesting qq:37391319
- # 技术支持 钉钉群: 21745728(可以加钉钉 pythontesting 邀请加入)
- # qq 群: 144081101 591302926 567351477
- # CreateDate: 2018-6-1
- import re
- from pypinyin import lazy_pinyin
- name = r'test.txt'
- text = open(name,encoding='utf-8').read()
- #print(text)
- results = re.findall(r'(:\d+)\s(.*?)\(\d+', text)
- names = set()
- for item in results:
- names.add(item[1])
- keys = list(names)
- keys = sorted(keys)
- def compare(char):
- try:
- result = lazy_pinyin(char)[0][0]
- except Exception as e:
- result = char
- return result
- keys.sort(key=compare)
- print(keys)
- #!python
- $ python3 qq.py
- ['Sally', '^^O^^', 'aa 催乳师', 'bling', '本草隐士', '纯中药治疗阳痿早泄', '长夜无荒', '东方~慈航', '干金草', '广东 - 曾超庆', '红梅 * 渝', 'jerryyu', '可怜的樱桃树', '叻风云', '欧阳 - 深圳白芒', '勝昔堂~元亨', '蜀中~ 眉豆.', '陕西渭南逸清阁 * 无为', '吴宁...... 任', '系统消息', '于立伟', '倚窗望岳', '烟霞霭霭', '燕子', '张强', '滋味', '买个罐头 吃西餐', '[大侠] 好好', '[大侠] 面向大海~纯中药治烫伤', '[宗师] 吴宁...... 任', '[宗师] 红梅 * 渝', '[少侠] 焚琴煮鹤', '[少侠] 笨笨', '[掌门] 溆浦山野人家']
来源: http://www.jianshu.com/p/12f271167aca