Python基础(十一) 异常处理

    在程序运行过程中,总会遇到各种各样的错误,有的错误是程序编写有问题造成的,比如本来应该输出整数结果输出了字符串,这样的错误我们通常称之为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  15 in  main
     bar( '0' )
   File  "E:/Python_project/SOCKET 代码/my_code.py" , line  11 in  bar
     return  foo(s)  *  2
   File  "E:/Python_project/SOCKET 代码/my_code.py" , line  8 in  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  13 in  <module>
     foo( '0' )
   File  "E:/Python_project/SOCKET 代码/my_code.py" , line  10 in  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。

 

转载于:https://www.cnblogs.com/phennry/p/5668092.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值