当前位置: 首页 > news >正文

Python基础之错误和异常讲解

文章目录

  • 1 错误和异常
    • 1.1 简介
      • 1.1.1 语法错误
      • 1.1.2 异常
    • 1.2 抛出异常
      • 1.2.1 抛出原装异常
      • 1.2.2 assert异常
      • 1.2.3 抛出用户自定义异常
    • 1.3 异常处理
      • 1.3.1 try/except
      • 1.3.2 try/except...else
      • 1.3.3 try-finally 语句
      • 1.3.4 with 关键字
    • 1.4 分析记录错误
      • 1.4.1 分析错误
      • 1.4.2 记录错误
    • 1.5 单元测试
      • 1.5.1 unittest单元测试
      • 1.5.2 setUp与tearDown
      • 1.5.3 文档测试

1 错误和异常

1.1 简介

Python 有两种错误很容易辨认:语法错误异常
Python assert(断言)用于判断一个表达式,在表达式条件为 false 的时候触发异常。

1.1.1 语法错误

Python 的语法错误或者称之为解析错,是初学者经常碰到的,如下实例

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

这个例子中,函数 print() 被检查到有错误,是它前面缺少了一个冒号 : 。
语法分析器指出了出错的一行,并且在最先找到的错误的位置标记了一个小小的箭头。

1.1.2 异常

即便 Python 程序的语法是正确的,在运行它的时候,也有可能发生错误。运行期检测到的错误被称为异常。

大多数的异常都不会被程序处理,都以错误信息的形式展现在这里:

>>> 10 * (1/0)             # 0 不能作为除数,触发异常
Traceback (most recent call last):File "<stdin>", line 1, in ?
ZeroDivisionError: division by zero
>>> 4 + spam*3             # spam 未定义,触发异常
Traceback (most recent call last):File "<stdin>", line 1, in ?
NameError: name 'spam' is not defined
>>> '2' + 2               # int 不能与 str 相加,触发异常
Traceback (most recent call last):File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "int") to str

异常以不同的类型出现,这些类型都作为信息的一部分打印出来: 例子中的类型有 ZeroDivisionError,NameError 和 TypeError。
错误信息的前面部分显示了异常发生的上下文,并以调用栈的形式显示具体信息。

1.2 抛出异常

1.2.1 抛出原装异常

Python 使用 raise 语句抛出一个指定的异常。
raise语法格式如下:raise [Exception [, args [, traceback]]]

以下实例如果 x 大于 5 就触发异常:

x = 10
if x > 5:raise Exception('x 不能大于 5。x 的值为: {}'.format(x))执行以上代码会触发异常:
Traceback (most recent call last):File "test.py", line 3, in <module>raise Exception('x 不能大于 5。x 的值为: {}'.format(x))
Exception: x 不能大于 5。x 的值为: 10

raise 唯一的一个参数指定了要被抛出的异常。它必须是一个异常的实例或者是异常的类(也就是 Exception 的子类)。

如果只想知道这是否抛出了一个异常,并不想去处理它,那么一个简单的 raise 语句就可以再次把它抛出。

>>> try:raise NameError('HiThere')  # 模拟一个异常。except NameError:print('An exception flew by!')raiseAn exception flew by!
Traceback (most recent call last):File "<stdin>", line 2, in ?
NameError: HiThere

1.2.2 assert异常

Python assert(断言)用于判断一个表达式,在表达式条件为 false 的时候触发异常。

断言可以在条件不满足程序运行的情况下直接返回错误,而不必等待程序运行后出现崩溃的情况,例如我们的代码只能在 Linux 系统下运行,可以先判断当前系统是否符合条件。

语法格式如下:assert expression或者附带参数assert expression [, arguments]
等价于:

if not expression:raise AssertionError
或者带参数
if not expression:raise AssertionError(arguments)

以下为 assert 使用实例:

>>> assert True     # 条件为 true 正常执行
>>> assert False    # 条件为 false 触发异常
Traceback (most recent call last):File "<stdin>", line 1, in <module>
AssertionError
>>> assert 1==1    # 条件为 true 正常执行
>>> assert 1==2    # 条件为 false 触发异常
Traceback (most recent call last):File "<stdin>", line 1, in <module>
AssertionError>>> assert 1==2, '1 不等于 2'
Traceback (most recent call last):File "<stdin>", line 1, in <module>
AssertionError: 1 不等于 2
>>>

以下实例判断当前系统是否为 Linux,如果不满足条件则直接触发异常,不必执行接下来的代码:

import sys
assert ('linux' in sys.platform), "该代码只能在 Linux 下执行"

1.2.3 抛出用户自定义异常

可以通过创建一个新的异常类来拥有自己的异常。异常类继承自 Exception 类,可以直接继承,或者间接继承,例如:

>>> class MyError(Exception):def __init__(self, value):self.value = valuedef __str__(self):return repr(self.value)>>> try:raise MyError(2*2)except MyError as e:print('My exception occurred, value:', e.value)My exception occurred, value: 4
>>> raise MyError('oops!')
Traceback (most recent call last):File "<stdin>", line 1, in ?
__main__.MyError: 'oops!'

在这个例子中,类 Exception 默认的 __init__() 被覆盖。
当创建一个模块有可能抛出多种不同的异常时,一种通常的做法是为这个包建立一个基础异常类,然后基于这个基础类为不同的错误情况创建不同的子类:

class Error(Exception):"""Base class for exceptions in this module."""passclass InputError(Error):"""Exception raised for errors in the input.Attributes:expression -- input expression in which the error occurredmessage -- explanation of the error"""def __init__(self, expression, message):self.expression = expressionself.message = messageclass TransitionError(Error):"""Raised when an operation attempts a state transition that's notallowed.Attributes:previous -- state at beginning of transitionnext -- attempted new statemessage -- explanation of why the specific transition is not allowed"""def __init__(self, previous, next, message):self.previous = previousself.next = nextself.message = message

大多数的异常的名字都以"Error"结尾,就跟标准的异常命名一样。

1.3 异常处理

1.3.1 try/except

异常捕捉可以使用 try/except 语句

以下例子中,让用户输入一个合法的整数,但是允许用户中断这个程序(使用 Control-C 或者操作系统提供的方法)。用户中断的信息会引发一个 KeyboardInterrupt 异常。

while True:try:x = int(input("请输入一个数字: "))breakexcept ValueError:print("您输入的不是数字,请再次尝试输入!")

try 语句按照如下方式工作;

  • 执行 try 子句(在关键字 try 和关键字 except 之间的语句)。
  • 如果没有异常发生,忽略 except 子句,try 子句执行后结束。
  • 如果在执行 try 子句的过程中发生了异常,那么 try 子句余下的部分将被忽略。如果异常的类型和 except 之后的名称相符,那么对应的 except 子句将被执行。
  • 如果一个异常没有与任何的 except 匹配,那么这个异常将会传递给上层的 try 中。

一个 try 语句可能包含多个except子句,分别来处理不同的特定的异常。最多只有一个分支会被执行。
处理程序将只针对对应的 try 子句中的异常进行处理,而不是其他的 try 的处理程序中的异常。

一个except子句可以同时处理多个异常,这些异常将被放在一个括号里成为一个元组,例如:

except (RuntimeError, TypeError, NameError):pass

最后一个except子句可以忽略异常的名称,它将被当作通配符使用。可以使用这种方法打印一个错误信息,然后使用raise再次把异常抛出。

import systry: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

1.3.2 try/except…else

try/except 语句还有一个可选的 else 子句,如果使用这个子句,那么必须放在所有的 except 子句之后。
else 子句将在 try 子句没有发生任何异常的时候执行。

以下实例在 try 语句中判断文件是否可以打开,如果打开文件时正常的没有发生异常则执行 else 部分的语句,读取文件内容:

for arg in sys.argv[1:]:try:f = open(arg, 'r')except IOError:print('cannot open', arg)else:print(arg, 'has', len(f.readlines()), 'lines')f.close()

使用 else 子句比把所有的语句都放在 try 子句里面要好,这样可以避免一些意想不到,而 except 又无法捕获的异常。

异常处理并不仅仅处理那些直接发生在 try 子句中的异常,而且还能处理子句中调用的函数(甚至间接调用的函数)里抛出的异常。例如:

>>> def this_fails():x = 1/0>>> try:this_fails()except ZeroDivisionError as err:print('Handling run-time error:', err)Handling run-time error: int division or modulo by zero

1.3.3 try-finally 语句

try-finally 语句无论是否发生异常都将执行最后的代码。
以下实例中 finally 语句无论异常是否发生都会执行:

try:runoob()
except AssertionError as error:print(error)
else:try:with open('file.log') as file:read_data = file.read()except FileNotFoundError as fnf_error:print(fnf_error)
finally:print('这句话,无论异常是否发生都会执行。')

下面是一个更加复杂的例子(在同一个 try 语句里包含 except 和 finally 子句):

>>> def divide(x, y):try:result = x / yexcept 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 ?File "<stdin>", line 3, in divide
TypeError: unsupported operand type(s) for /: 'str' and 'str'

1.3.4 with 关键字

Python 中的 with 语句用于异常处理,封装了 try…except…finally 编码范式,提高了易用性。
with 语句使代码更清晰、更具可读性, 它简化了文件流等公共资源的管理。
在处理文件对象时使用 with 关键字是一种很好的做法。

我们可以看下以下几种代码实例:不使用 with,也不使用 try…except…finally

file = open('./test_runoob.txt', 'w')
file.write('hello world !')
file.close()

以上代码如果在调用 write 的过程中,出现了异常,则 close 方法将无法被执行,因此资源就会一直被该程序占用而无法被释放。 接下来我们呢可以使用 try…except…finally 来改进代码:

file = open('./test_runoob.txt', 'w')
try:file.write('hello world')
finally:file.close()

以上代码我们对可能发生异常的代码处进行 try 捕获,发生异常时执行 except 代码块,finally 代码块是无论什么情况都会执行,所以文件会被关闭,不会因为执行异常而占用资源。

使用 with 关键字:

with open('./test_runoob.txt', 'w') as file:file.write('hello world !')

使用 with 关键字系统会自动调用 f.close() 方法, with 的作用等效于 try/finally 语句是一样的。

我们可以在执行 with 关键字后检验文件是否关闭:

>>> with open('./test_runoob.txt') as f:
...     read_data = f.read()>>> # 查看文件是否关闭
>>> f.closed
True

with 语句实现原理建立在上下文管理器之上。上下文管理器是一个实现 __enter____exit__ 方法的类。使用 with 语句确保在嵌套块的末尾调用 __exit__ 方法。这个概念类似于 try…finally 块的使用。

with open('./test_runoob.txt', 'w') as my_file:my_file.write('hello world!')

以上实例将 hello world! 写到 ./test_runoob.txt 文件上。

在文件对象中定义了 __enter____exit__ 方法,即文件对象也实现了上下文管理器,首先调用 __enter__ 方法,然后执行 with 语句中的代码,最后调用 __exit__ 方法。 即使出现错误,也会调用 __exit__ 方法,也就是会关闭文件流。

1.4 分析记录错误

1.4.1 分析错误

如果错误没有被捕获,它就会一直往上抛,最后被Python解释器捕获,打印一个错误信息,然后程序退出。来看看err.py:

# err.py:
def foo(s):return 10 / int(s)def bar(s):return foo(s) * 2def main():bar('0')main()
执行,结果如下:$ python err.py
Traceback (most recent call last):File "err.py", line 11, in <module>main()File "err.py", line 9, in mainbar('0')File "err.py", line 6, in barreturn foo(s) * 2File "err.py", line 3, in fooreturn 10 / int(s)
ZeroDivisionError: integer division or modulo by zero

出错并不可怕,可怕的是不知道哪里出错了。解读错误信息是定位错误的关键。我们从上往下可以看到整个错误的调用函数链:
错误信息第1行:

Traceback (most recent call last):

告诉我们这是错误的跟踪信息。
第2行:

  File "err.py", line 11, in <module>main()

调用main()出错了,在代码文件err.py的第11行代码,但原因是第9行:

  File "err.py", line 9, in mainbar('0')

调用bar(‘0’)出错了,在代码文件err.py的第9行代码,但原因是第6行:

  File "err.py", line 6, in barreturn foo(s) * 2

原因是return foo(s) * 2这个语句出错了,但这还不是最终原因,继续往下看:

  File "err.py", line 3, in fooreturn 10 / int(s)

原因是return 10 / int(s)这个语句出错了,这是错误产生的源头,因为下面打印了:

ZeroDivisionError: integer division or modulo by zero

根据错误类型ZeroDivisionError,我们判断,int(s)本身并没有出错,但是int(s)返回0,在计算10 / 0时出错,至此,找到错误源头。

1.4.2 记录错误

如果不捕获错误,自然可以让Python解释器来打印出错误堆栈,但程序也被结束了。既然我们能捕获错误,就可以把错误堆栈打印出来,然后分析错误原因,同时,让程序继续执行下去。

Python内置的logging模块可以非常容易地记录错误信息:

# err.py
import loggingdef foo(s):return 10 / int(s)def bar(s):return foo(s) * 2def main():try:bar('0')except StandardError, e:logging.exception(e)main()
print 'END'

同样是出错,但程序打印完错误信息后会继续执行,并正常退出:

$ python err.py
ERROR:root:integer division or modulo by zero
Traceback (most recent call last):File "err.py", line 12, in mainbar('0')File "err.py", line 8, in barreturn foo(s) * 2File "err.py", line 5, in fooreturn 10 / int(s)
ZeroDivisionError: integer division or modulo by zero
END

通过配置,logging还可以把错误记录到日志文件里,方便事后排查。

1.5 单元测试

1.5.1 unittest单元测试

为了编写单元测试,我们需要引入Python自带的unittest模块,编写mydict_test.py如下:

import unittest
from mydict import Dictclass TestDict(unittest.TestCase):def test_init(self):d = Dict(a=1, b='test')self.assertEquals(d.a, 1)self.assertEquals(d.b, 'test')self.assertTrue(isinstance(d, dict))def test_key(self):d = Dict()d['key'] = 'value'self.assertEquals(d.key, 'value')def test_attr(self):d = Dict()d.key = 'value'self.assertTrue('key' in d)self.assertEquals(d['key'], 'value')def test_keyerror(self):d = Dict()with self.assertRaises(KeyError):value = d['empty']def test_attrerror(self):d = Dict()with self.assertRaises(AttributeError):value = d.empty

编写单元测试时,我们需要编写一个测试类,从unittest.TestCase继承。
以test开头的方法就是测试方法,不以test开头的方法不被认为是测试方法,测试的时候不会被执行。

对每一类测试都需要编写一个test_xxx()方法。由于unittest.TestCase提供了很多内置的条件判断,我们只需要调用这些方法就可以断言输出是否是我们所期望的。最常用的断言就是assertEquals()self.assertEquals(abs(-1), 1) , 断言函数返回的结果与1相等

另一种重要的断言就是期待抛出指定类型的Error,比如通过d[‘empty’]访问不存在的key时,断言会抛出KeyError:

with self.assertRaises(KeyError):value = d['empty']

而通过d.empty访问不存在的key时,我们期待抛出AttributeError:

with self.assertRaises(AttributeError):value = d.empty

运行单元测试
一旦编写好单元测试,我们就可以运行单元测试。最简单的运行方式是在mydict_test.py的最后加上两行代码:

if __name__ == '__main__':unittest.main()

这样就可以把mydict_test.py当做正常的python脚本运行,另一种更常见的方法是在命令行通过参数-m unittest直接运行单元测试:$ python -m unittest mydict_test

1.5.2 setUp与tearDown

可以在单元测试中编写两个特殊的setUp()和tearDown()方法。这两个方法会分别在每调用一个测试方法的前后分别被执行。

setUp()和tearDown()方法有什么用呢?设想测试需要启动一个数据库,这时,就可以在setUp()方法中连接数据库,在tearDown()方法中关闭数据库,这样,不必在每个测试方法中重复相同的代码:

class TestDict(unittest.TestCase):def setUp(self):print 'setUp...'def tearDown(self):print 'tearDown...'

可以再次运行测试看看每个测试方法调用前后是否会打印出setUp…和tearDown…。

1.5.3 文档测试

当我们编写注释时,如果写上这样的注释:

def abs(n):'''Function to get absolute value of number.Example:>>> abs(1)1>>> abs(-1)1>>> abs(0)0'''return n if n >= 0 else (-n)

无疑更明确地告诉函数的调用者该函数的期望输入和输出。并且,Python内置的文档测试(doctest)模块可以直接提取注释中的代码并执行测试。
doctest严格按照Python交互式命令行的输入和输出来判断测试结果是否正确。只有测试异常的时候,可以用…表示中间一大段烦人的输出。

用doctest来测试上次编写的Dict类:

class Dict(dict):'''Simple dict but also support access as x.y style.>>> d1 = Dict()>>> d1['x'] = 100>>> d1.x100>>> d1.y = 200>>> d1['y']200>>> d2 = Dict(a=1, b=2, c='3')>>> d2.c'3'>>> d2['empty']Traceback (most recent call last):...KeyError: 'empty'>>> d2.emptyTraceback (most recent call last):...AttributeError: 'Dict' object has no attribute 'empty''''def __init__(self, **kw):super(Dict, self).__init__(**kw)def __getattr__(self, key):try:return self[key]except KeyError:raise AttributeError(r"'Dict' object has no attribute '%s'" % key)def __setattr__(self, key, value):self[key] = valueif __name__=='__main__':import doctestdoctest.testmod()运行python mydict.py:
$ python mydict.py

什么输出也没有。这说明我们编写的doctest运行都是正确的。如果程序有问题,比如把__getattr__()方法注释掉,再运行就会报错:

$ python mydict.py
**********************************************************************
File "mydict.py", line 7, in __main__.Dict
Failed example:d1.x
Exception raised:Traceback (most recent call last):...AttributeError: 'Dict' object has no attribute 'x'
**********************************************************************
File "mydict.py", line 13, in __main__.Dict
Failed example:d2.c
Exception raised:Traceback (most recent call last):...AttributeError: 'Dict' object has no attribute 'c'
**********************************************************************

注意到最后两行代码。当模块正常导入时,doctest不会被执行。只有在命令行运行时,才执行doctest。所以,不必担心doctest会在非测试环境下执行。

相关文章:

  • ArkTS自定义组件
  • 【D3.js in Action 3 精译】1.2.2 可缩放矢量图形(三)
  • GCP FrontendConfig 详解:优化您的云负载均衡
  • 自然语言处理-BERT处理框架-transformer
  • centos 7系统升级内核(ELRepo仓库)、小版本升级、自编译内核
  • Element-plus点击当前行之后获取数据显示跟随行数据
  • Java将list数组中重复的对象进行去重
  • java反射和注解
  • 基于Spring Boot与Vue的智能房产匹配平台+文档
  • Log4j日志框架讲解(全面,详细)
  • 上帝之眼(BEVSee):多相机间无需标定,将各自目标统一到同一坐标系下(代码开源,提供数据集)
  • C++编程(五)单例模式 友元
  • 如何寻找一个领域的顶级会议,并且判断这个会议的影响力?
  • SpringBoot学习04-[定制SpringMVC]
  • 在Qt中,直接include <moc_xxxxx.cpp> 为什么不会出现符号冲突的错误?
  • 【402天】跃迁之路——程序员高效学习方法论探索系列(实验阶段159-2018.03.14)...
  • 【划重点】MySQL技术内幕:InnoDB存储引擎
  • 【知识碎片】第三方登录弹窗效果
  • EventListener原理
  • Java,console输出实时的转向GUI textbox
  • Koa2 之文件上传下载
  • Laravel深入学习6 - 应用体系结构:解耦事件处理器
  • magento 货币换算
  • TCP拥塞控制
  • 基于Vue2全家桶的移动端AppDEMO实现
  • 盘点那些不知名却常用的 Git 操作
  • 前端技术周刊 2018-12-10:前端自动化测试
  • 如何选择开源的机器学习框架?
  • 世界编程语言排行榜2008年06月(ActionScript 挺进20强)
  • 源码安装memcached和php memcache扩展
  • 如何用纯 CSS 创作一个菱形 loader 动画
  • ​Java并发新构件之Exchanger
  • ###STL(标准模板库)
  • #QT(智能家居界面-界面切换)
  • #微信小程序(布局、渲染层基础知识)
  • (14)目标检测_SSD训练代码基于pytorch搭建代码
  • (175)FPGA门控时钟技术
  • (3)llvm ir转换过程
  • (笔记)Kotlin——Android封装ViewBinding之二 优化
  • (附源码)springboot电竞专题网站 毕业设计 641314
  • (个人笔记质量不佳)SQL 左连接、右连接、内连接的区别
  • (九)c52学习之旅-定时器
  • (数据大屏)(Hadoop)基于SSM框架的学院校友管理系统的设计与实现+文档
  • (原+转)Ubuntu16.04软件中心闪退及wifi消失
  • (总结)Linux下的暴力密码在线破解工具Hydra详解
  • ******IT公司面试题汇总+优秀技术博客汇总
  • **python多态
  • .net mvc 获取url中controller和action
  • .NET 反射 Reflect
  • .net打印*三角形
  • .NET企业级应用架构设计系列之结尾篇
  • ::前边啥也没有
  • ??如何把JavaScript脚本中的参数传到java代码段中
  • [ 英语 ] 马斯克抱水槽“入主”推特总部中那句 Let that sink in 到底是什么梗?
  • []sim300 GPRS数据收发程序