在程序运行过程中,总会遇到各种各样的错误,有的错误是程序编写有问题造成的,比如本来应该输出整数结果输出了字符串,这样的错误我们通常称之为BUG,BUG是必须修复的。在Python中内置了一套异常处理机制,来帮助我们进行过错误处理,此外我们也需要跟踪程序的执行,查看变量的值是否正确,这个过程称为调试。Python的pdb可以让我们以单步方式执行代码。下面开始今天的内容

一、捕获错误

1、异常介绍

在编程过程中为了增强友好性,在程序出现bug时一般不会讲错误信息显示给用户,而是现实一个提示的页面,通俗的来说就是不让用户看见大黄页。

1
2
3
4
try:
    pass
except Exception as ex:
    pass

需求:将用户输入的两个数字相加。

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python
#-*- coding:utf-8 -*-
  
while True:
    num1 = input('input num1:')
    num2 = input('input num2:')
    try:
        num1 = int(num1)
        num2 = int(num2)
    except Exception as e:          #捕获任何异常
        print('出现异常,信息如下:')
        print(e)
 
#结果:
input num1:test
input num2:1
出现异常,信息如下:
invalid literal for int() with base 10'test'

2、异常种类

python中的异常种类非常多,每个异常专门用于处理某一个项异常。

 
1
2
3
4
5
6
7
8
9
10
11
12
13
AttributeError     #试图访问一个对象没有的树形,比如foo.x,但是foo没有属性x
IOError            #输入/输出异常;基本上是无法打开文件
ImportError        #无法引入模块或包;基本上是路径问题或名称错误
IndentationError   #语法错误(的子类) ;代码没有正确对齐
IndexError         #下标索引超出序列边界,比如当x只有三个元素,却试图访问x[5]
KeyError           #试图访问字典里不存在的键
KeyboardInterrupt  #Ctrl+C被按下
NameError          #使用一个还未被赋予对象的变量
SyntaxError Python #代码非法,代码不能编译(个人认为这是语法错误,写错了)
TypeError          #传入对象类型与要求的不符合
UnboundLocalError  #试图访问一个还未被设置的局部变量,基本上是由于另有一个同名的全局变量,导致你以为正在访问它
ValueError         #传入一个调用者不期望的值,即使值的类型是正确的

更多异常请参考官网地址:https://docs.python.org/3/library/exceptions.html#exception-hierarchy

下面举几个捕获异常的实例:

捕获IndexError错误:

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/env python
#-*- coding:utf-8 -*-
  
dic = ["jack","eric"]
try:
    dic[10]
except IndexError as e:
    print(e)
 
#结果:
list index out of range

捕获KeyError错误:

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/env python
#-*- coding:utf-8 -*-
 
dic = {'k1':'v1'}
try:
    dic['k20']
except KeyError as e:    #捕获KeyError
    print(e)
 
#结果:
'k20'

捕获ValueError错误:

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/env python
#-*- coding:utf-8 -*-
 
s1 = 'hello'
try:
    int(s1)
except ValueError as e:
    print(e)
 
#结果:
invalid literal for int() with base 10'hello'

对于上述实例,异常类只能用来处理指定的异常情况,如果非指定异常则无法处理。

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/env python
#-*- coding:utf-8 -*-
 
 
s1 = 'hello'
try:
    int(s1)
except IndexError as e:   #如果未捕获到异常,程序直接报错
    print(e)
 

所以,写程序时需要考虑到Try代码块中可能出现的任意异常,这时可能想到我把能想到的错误都预先写好,但这不是最佳的解决办法,在Python中给我们提供了一个捕获万能异常的参数:Exception,它可以捕获任意异常,即:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env python
#-*- coding:utf-8 -*-
 
 
def foo(s):
    return 10 / int(s)
 
def bar(s):
    return foo(s) * 2
 
def main():
    try:
        bar("0")
    except Exception as e:    #捕获任意异常
        print('Error:',e)
 
if __name__=='__main__':
    main()
 
#结果:
Error: division by zero

接下来你可能要问了,既然有这个万能异常,其他异常是不是就可以忽略了,NO!!对于特殊处理货提醒的异常需要先定义,最后定义Exception来确保程序正常运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env python
#-*- coding:utf-8 -*-
 
s1 = 'hello'
try:
    int(s1)
except KeyError as e:
    print('键错误')
except IndexError as e:
    print('索引错误')
except Exception as e:
    print('错误')
 
#结果:
错误

异常还有个更高级的结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/usr/bin/env python
#-*- coding:utf-8 -*-
 
def foo(s):
    return 10 / int(s)
 
def bar(s):
    return foo(s) * 2
 
def main():
    try:                       #主代码块
        bar('0')
    except Exception as e:     #异常时,执行该步骤
        print('Error:', e)
    else:                      #主代码块执行完,不出错执行该步骤
        print('else....')
    finally:                   #无论异常与否,最终执行该步骤
        print('finally...')
 
if __name__=='__main__':
    main()
 
#结果:
Error: division by zero
finally...

二、记录错误

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

我们之前介绍过日志模块logging,可以非常容易地记录错误信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/env python
#-*- coding:utf-8 -*-
 
 
import logging
 
def foo(s):
    return 10 / int(s)
 
def bar(s):
    return foo(s) * 2
 
def main():
    try:
        bar('0')
    except Exception as e:
        logging.exception(e)
 
 
 
main()
print('END')

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

1
2
3
4
5
6
7
8
9
10
ERROR:root:division by zero
Traceback (most recent call last):
  File "E:/Python_project/SOCKET 代码/my_code.py", line 15in main
    bar('0')
  File "E:/Python_project/SOCKET 代码/my_code.py", line 11in bar
    return foo(s) * 2
  File "E:/Python_project/SOCKET 代码/my_code.py", line 8in foo
    return 10 / int(s)
ZeroDivisionError: division by zero
END

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

三、抛出错误

因为错误是Class,捕获一个错误就是捕获到该class的一个实例。因此,错误并不是凭空产生的,而是有意创建并抛出的。Python的内置函数会抛出很多类型的错误,我们自己编写的函数也可以抛出错误。

如果要抛出错误,首先根据需要,可以定义一个错误的Class,选择好继承关系,然后用raise语句抛出一个错误的实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/env python
#-*- coding:utf-8 -*-
 
class Foo(ValueError):
    pass
 
def foo(s):
    = int(s)
    if == 0:
        raise Foo('invalid value:%s'%s)   #调用raise,抛出异常
    return 10 / n
 
foo('0')

执行上面的代码,我们可以跟踪到我们定义的错误:

1
2
3
4
5
6
Traceback (most recent call last):
  File "E:/Python_project/SOCKET 代码/my_code.py", line 13in <module>
    foo('0')
  File "E:/Python_project/SOCKET 代码/my_code.py", line 10in foo
    raise Foo('invalid value:%s'%s)
__main__.Foo: invalid value:0

只有在必要的时候才定义我们自己的错误类型,如果可以选择Python已有的内置的错误类型(比如ValueError,TyprError),尽量使用Python内置的错误类型。

1
2
3
4
5
6
7
8
9
10
11
12
class MyException(Exception):
  
    def __init__(self, msg):
        self.message = msg
  
    def __str__(self):
        return self.message
  
try:
    raise MyException('我的异常')
except MyException as e:
    print(e)

四、单元测试

1、如何编写单元测试

首先来介绍一个概念(断言),凡是用print()来辅助查看的地方,都可以用断言(assert)来替代。

单元测试是用来对一个模块、一个函数或者一个类进行正确性检验的测试工作。

比如对内置函数abs(),我们可以编写出一下几个测试用例:

  • 输入整数,比如1、1.2、0.99,期待返回值与输入相同;

  • 输入负数,比如-1、-1.2、-0.99,期待返回值与输入相反;

  • 输入0,期待返回0;

  • 输入非数值类型,比如None、[]、{},期待抛出TypeError。

把上面的测试用例放到一个测试模块里,就是一个完整的单元测试。

如果单元测试通过,说明我们测试的这个函数能够正常工作。如果单元测试不通过,要么函数有bug,要么测试条件输入不正确,总之,需要修复使单元测试能够通过。

单元测试通过后有什么意义呢?

如果我们队abs()函数代码做了修改,只需要跑一遍单元测试,如果通过,说明我们的修改不会对abs()函数原有的行为造成影响,如果测试不通过,说明测试不通过,说明我们的修改与原有行为不一致,要么修改代码,要么修改测试。这种以测试为驱动的开发模式最大的好处就是确保一个程序模块的行为符合我们设计的测试用例。在将来修改的时候,可以极大程度地保证该模块行为仍是正确的。

下面我们来编写一个Dict类,这个类的行为和dict一致,但是可以通过属性来访问,用起来就像下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#自己定义的代码mydict.py
class Dict(dict):
 
    def __init__(self**kw):
        super().__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] = value

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import unittest
 
from mydict import Dict
 
class TestDict(unittest.TestCase):
 
    def test_init(self):
        = Dict(a=1, b='test')
        self.assertEqual(d.a, 1)
        self.assertEqual(d.b, 'test')
        self.assertTrue(isinstance(d, dict))
 
    def test_key(self):
        = Dict()
        d['key'= 'value'
        self.assertEqual(d.key, 'value')
 
    def test_attr(self):
        = Dict()
        d.key = 'value'
        self.assertTrue('key' in d)
        self.assertEqual(d['key'], 'value')
 
    def test_keyerror(self):
        = Dict()
        with self.assertRaises(KeyError):
            value = d['empty']
 
    def test_attrerror(self):
        = Dict()
        with self.assertRaises(AttributeError):
            value = d.empty

编写单元测试时,我们需要编写一个测试类,从unittest.TestCase继承。

以test开头的方法就是测试方法,不以test开头的方法不被认为是测试方法,测试的时候不会被执行。

对每一类测试都需要编写一个test_xx()方法。由于unittest.TestCase提供了很多内置的条件判断,我们只需要调用这些方法就可以断言输出是否是我们所期望的,最常用的断言就是assertEqual():

1
self.assertEqual(abs(-1),1)      #断言函数返回的结果与1相等

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

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

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

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

2、运行单元测试

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

 
1
2
3
4
5
6
7
8
9
if __name__ == '__main__':
    unittest.main()
 
#执行结果:
.....
----------------------------------------------------------------------
Ran 5 tests in 0.000s
 
OK

3、setUp与tearDown方法

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

当我们测试需要连接启动一个数据库,这时就可以在setUp()方法中连接数据库,在tearDown()方法中关闭数据库,这样不必再每个测试方法中重复相同的代码。

1
2
3
4
5
6
7
class TestDict(unittest.TestCase):
 
    def setUp(self):
        print('setUp...')
 
    def tearDown(self):
        print('tearDown...')

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

总结一下,今天我们主要介绍了如何捕获错误信息,记录错误和单元测试,单元测试在我们日后的工作中很是重要,可以检测自己写的程序是否有BUG,单元测试通过了并不意味着程序就没有BUG了,单数不通过程序肯定有BUG。

Python基础(十一) 异常处理的更多相关文章

  1. Python 基础之 异常处理

    python 基础之异常处理 说到异常处理,就得先问一下,什么是异常处理?  先来看一下,什么是异常? 异常就是:程序运行时发出的错误的信号. 异常的种类先来看一下: 一.常见的异常 Attribut ...

  2. python基础之异常处理

    Python3 错误和异常 作为Python初学者,在刚学习Python编程时,经常会看到一些报错信息,在前面我们没有提及,这章节我们会专门介绍. Python有两种错误很容易辨认:语法错误和异常. ...

  3. Python基础、异常处理

    一.概述 错误与异常概念 异常也是对象, 基于Exception类.内置异常 异常处理.流程 try/except/else  处理python或你触发的异常 try/fianlly   不管有没有异 ...

  4. Python基础(十一) 类继承

    类继承: 继承的想法在于,充份利用已有类的功能,在其基础上来扩展来定义新的类. Parent Class(父类) 与 Child Class(子类): 被继承的类称为父类,继承的类称为子类,一个父类, ...

  5. Python基础_异常处理与跟踪

    异常的种类 AttributeError 试图访问一个对象没有的树形,比如foo.x,但是foo没有属性x IOError 输入/输出异常:基本上是无法打开文件 ImportError 无法引入模块或 ...

  6. Day8 - Python基础8 异常处理、反射、单例模式

    本节内容: 1:异常处理 2:反射 3:单例模式 1.异常处理  1.异常简介 在编程过程中为了增加友好性,在程序出现bug时一般不会将错误信息显示给用户,而是现实一个提示的页面,通俗来说就是不让用户 ...

  7. python基础(21):异常处理

    1. 异常和错误 1.1 错误 程序中难免出现错误,而错误分成两种 1.1.1 语法错误 语法错误:这种错误,根本过不了python解释器的语法检测,必须在程序执行前就改正. #语法错误示范一 if ...

  8. python 基础(七) 异常处理

    异常处理 一.需求 当遇到错误的时候 不让程序停止执行 而是越过错误继续执行 二.主体结构 (抓取所有异常) try:   可能出现异常的代码段 except:   出现异常以后的处理   三.处理特 ...

  9. python 基础(十一) pickle 序列化

    一.pickle序列化的操作 使用说明:可以将数据 转换成2进制 写入到文件中 或者之间返回 做到将数据原样写入 原样取出 import pickle (1) dump 写入文件中 pickle.du ...

  10. python基础十一之迭代器和生成器

    可迭代 内置方法中含有__iter__的数据类型都是可迭代的,只要是可迭代的就可以使用for循环,反之亦然. print(dir('')) # dir()函数可以获取当前数据类型的所有内置方法 返回值 ...

随机推荐

  1. 如何给mysql用户分配权限+增、删、改、查mysql用户

    在mysql中用户权限是一个很重析 参数,因为台mysql服务器中会有大量的用户,每个用户的权限需要不一样的,下面我来介绍如何给mysql用户分配权限吧,有需要了解的朋友可参考. 1,Mysql下创建 ...

  2. 配置URL

  3. Kettle 连接 oracle 报错:could not be found, make sure the 'Oracle' driver (jar file) is installed.

    我的ETL版本为6.0 oracle版本为11.2.0 报错如下: Driver class 'oracle.jdbc.driver.OracleDriver' could not be found, ...

  4. html表格合并单元格

    th标签 合并列 colspan="k" 合并行 rowspan="k"   例子<th colspan="2", rowspan=& ...

  5. bzoj 1682: [Usaco2005 Mar]Out of Hay 干草危机【并查集+二分】

    二分答案,把边权小于mid的边的两端点都并起来,看最后是否只剩一个联通块 #include<iostream> #include<cstdio> using namespace ...

  6. jQuery——表单应用(4)

    HTML: <!--复选框应用--> <!DOCTYPE html> <html> <head> <meta charset="UTF- ...

  7. SVN安装失败提示

    svnserve: error while loading shared libraries: libaprutil-1.so.0: cannot open shared object file: 1 ...

  8. 【js】JavaScript parser实现浅析

    最近笔者的团队迁移了webpack2,在迁移过程中,笔者发现webpack2中有相当多的兼容代码,虽然外界有很多声音一直在质疑作者为什么要破坏性更新,其实大家也都知道webpack1那种过于“灵活”的 ...

  9. 51nod2006 飞行员配对(二分图最大匹配)

    2006 飞行员配对(二分图最大匹配) 题目来源: 网络流24题 基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题  收藏  关注 第二次世界大战时期,英国皇家空军从沦陷国 ...

  10. js作用域、异步——学习笔记

    所有的 for if switch while do 等等,都属于块级作用域,里面声明的对象,外面也能访问.但function 函数里的作用域,在函数外是访问不到的. 但函数作用域里面可以访问外面. ...