异常处理结构与程序调试、测试
1. 基本概念
简单来说,异常是指程序运行时引发的错误,引发错误的原因有很多,例如除0、下标越界、文件不存在、网络异常、类型错误、名字错误、字典键错误、磁盘空间不足等
-
如果这些错误得不到正确的处理会导致程序终止运行,合理利用异常处理机制可以使得程序具有更强的容错性,程序更加健壮。不会因为一些具体原因比如用户错误的输入而导致程序终止
-
可以使用异常处理结构为用户提供更友好的提示。
-
异常的示例
x,y=10,5
a=x/y
print(A) # 拼写错误python区分变量名等标识符字母的大小写NameError
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-1-2d3eb8f13e8e> in <module>
1 x,y=10,5
2 a=x/y
----> 3 print(A) # 拼写错误python区分变量名等标识符字母的大小写NameError
NameError: name 'A' is not defined
10/0 # 除0错误(math Exception)ZeroDivisionError
'2' + 2 #对象类型不支持特定的操作 TypeError
2.Python异常类与自定义异常
内置异常的类层级结构如下:
BaseException
+-- SystemExit
+-- KeyboardInterrupt
+-- GeneratorExit
+-- Exception
+-- StopIteration
+-- StopAsyncIteration
+-- ArithmeticError
| +-- FloatingPointError
| +-- OverflowError
| +-- ZeroDivisionError
+-- AssertionError
+-- AttributeError
+-- BufferError
+-- EOFError
+-- ImportError
| +-- ModuleNotFoundError
+-- LookupError
| +-- IndexError
| +-- KeyError
+-- MemoryError
+-- NameError
| +-- UnboundLocalError
+-- OSError
| +-- BlockingIOError
| +-- ChildProcessError
| +-- ConnectionError
| | +-- BrokenPipeError
| | +-- ConnectionAbortedError
| | +-- ConnectionRefusedError
| | +-- ConnectionResetError
| +-- FileExistsError
| +-- FileNotFoundError
| +-- InterruptedError
| +-- IsADirectoryError
| +-- NotADirectoryError
| +-- PermissionError
| +-- ProcessLookupError
| +-- TimeoutError
+-- ReferenceError
+-- RuntimeError
| +-- NotImplementedError
| +-- RecursionError
+-- SyntaxError
| +-- IndentationError
| +-- TabError
+-- SystemError
+-- TypeError
+-- ValueError
| +-- UnicodeError
| +-- UnicodeDecodeError
| +-- UnicodeEncodeError
| +-- UnicodeTranslateError
+-- Warning
+-- DeprecationWarning
+-- PendingDeprecationWarning
+-- RuntimeWarning
+-- SyntaxWarning
+-- UserWarning
+-- FutureWarning
+-- ImportWarning
+-- UnicodeWarning
+-- BytesWarning
+-- ResourceWarning
- 如果需要,可以继承Python内置异常类来实现自定义的异常类(官方推荐继承Exception类而非BaseException)
exception Exception
所有内置的非系统退出类异常都派生自此类。 所有用户自定义异常也应当派生自此类。
from pip._vendor.distlib.compat import raw_input
class ShortInputException(Exception):
"""自定义异常类"""
def __init__(self, length, atleast):
Exception.__init__(self)
self.length = length
self.atleast = atleast
try:
s = raw_input('请输入-->')
if len(s) < 3:
raise ShortInputException(len(s), 3)
except EOFError:
print('您输入了一个结束标记EOF')
except ShortInputException as x:
print("ShortInputException:输入的长度是%d,长度至少应该是%d" % (x.length, x.atleast))
else:
print('没有异常发生')
class MyError(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return repr(self.value)
try:
raise MyError(2 * 2)
except MyError as e: # 使用except 类名 as 变量名来将e作为MyError的实例化对象
print('My Exception occurred,value:', e.value)
raise MyError('oops!')
- 如果自己编写的某个模块需要抛出多个不同但相关的异常,可以定义一个异常基类,然后创建多个派生类分别表示不同的异常
class Error(Exception):
"""创建基类"""
pass
class InputError(Error):
"""Exception raised for errors in the input"""
def __init__(self, expression, message):
"""
:param expression:input expression in which the error occured
:param message:explanation of the error
"""
self.expression = expression
self.message = message
class TransitionError(Error):
"""派生类TransitionError"""
def __init__(self, previous, next, message):
"""
:param previous:state at beginning of transition
:param next:attempted new state
:param message:explanation of why the specific transition is not allowed
"""
self.previous = previous
self.next = next
self.message = message
3.Python中的异常处理结构
3.1 try…except结构(在其他语言中大多是try-catch)
```
try:
try块 # 被监控的语句,可能会引发异常
except Exception[as reason]:
except块 # 处理异常的代码
```
- 如果需要捕获所有类型的异常,可以使用BaseException(多态的原理),即Python异常类的基类
try: try块 # 被监控的语句,可能会引发异常 except BaseException as e: except块 # 处理所有错误
- 一般不建议这样做,尽量显示捕捉可能会出现的异常,并且有针对性地编码进行处理,当然,为了避免遗漏没有得到处理的异常干扰程序的正常执行,在捕捉了所有可能的异常后,也可以使用异常处理结构的最后一个except来捕捉BaseException
# 提示用户输入内容,如果输入的是数字,则循环结束,否则一直提示用户输入正确的格式
while True:
try:
x=int(input("Please enter a number"))
break
except ValueError:
print('your input was not a valid number,Please try again..')
Please enter a numbera
your input was not a valid number,Please try again..
Please enter a numberv
your input was not a valid number,Please try again..
Please enter a numbers
your input was not a valid number,Please try again..
Please enter a number1
# 在使用时,通过在except子句后面异常类名字指定一个变量来捕获异常的参数或更详细的信息
try:
raise Exception('spam','eggs')
except Exception as inst:
print(type(inst)) # 查看inst的具体类
print(inst.args) # 查看异常类中存储的具体参数
print(inst) # 这个根据该异常类的__str__方法内容来决定
x,y=inst.args #参数解包
print("x=",x)
print("y=",y)
<class 'Exception'>
('spam', 'eggs')
('spam', 'eggs')
x= spam
y= eggs
3.2 try–except–else 结构
- 如果try中的代码抛出了异常,并且被某个except捕捉,则执行相应的异常处理代码而不会执行else中的方法
- 如果try中代码没有抛出任何异常,则执行else块中的代码
a_list = ['China','America','England','France']
print("请输入字符串的序号:")
while True:
try:
n=int(input())
print(a_list[n])
except IndexError:
print("列表元素的下标越界或格式不正确,请重写输入字符串序号")
else:
break # 没有出现异常,结束循环
请输入字符串的序号:
5
列表元素的下标越界或格式不正确,请重写输入字符串序号
6
列表元素的下标越界或格式不正确,请重写输入字符串序号
2
England
3.3带有多个except的try结构
- 在实际开发中,同一段代码可能会抛出多个异常,需要针对不同的异常类型进行相应的处理。
- 为了支持多个异常的捕获和处理,Python提供了带有多个except的异常处理结构,类似于多分支选择结构,一旦某个except捕获了异常,后面剩余的except子句将不会再执行
try: try块 # 被监控的语句 except Exception1: except块1 # 处理异常1的语句 except Exception2: except块2 # 处理异常2的语句
try:
x=input("请输入被除数:")
y=input("请输入除数:")
z = float(x)/float(y)
except ZeroDivisionError:
print("除数不能为0")
except ValueError:
print("被除数和除数应该为数值类型")
except NameError as e:
print(e.args,"变量不存在")
else:
print(x,'/',y,'=',z)
请输入被除数:a
请输入除数:v
被除数和除数应该为数值类型
- 将要捕获的异常写在一个元组中,可以使用一个except语句捕获多个异常,而且共用一段异常处理代码
import sys
try:
f = open('myfile.txt')
s=f.readline()
i = int(s.strip())
except (OSError,ValueError,RuntimeError,NameError):
print("错误")
pass
错误
3.4 try–except–finally结构
- 在该结构中,finally子句中的语句块不论异常是否发生都会被执行,常用于做一些清理工作以释放try子句中申请的资源
try:
3/0
except Exception:
print(3)
finally:
print(5)
3
5
## 不管读取文件是否异常,总能保证正常关闭该文件io流
try:
fp=open(r'D:\sample.txt','r')
while True:
line = fp.readline()
if line=="":
break
print(line)
except IOError:
print("读取异常")
finally:
fp.close()
print("已关闭输入流")
talk is cheap show me the code
hello world
hello world
已关闭输入流
- 需要注意的是,如果try子句中的异常没有被捕获和正确处理,那么这些异常会在finally子句执行完后再次抛出
def divide(x,y):
try:
result = x/y
except ZeroDivisionError:
print("division by zero!")
else:
print("result is ",result) # 如果没有异常,才会执行else子句
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") # 没有捕获的异常,在finally执行完后还是会抛出异常
executing finally clause
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-26-1997ae95ac07> in <module>
----> 1 divide("2","1") # 没有捕获的异常,在finally执行完后还是会抛出异常
<ipython-input-23-1e7060b55e98> in divide(x, y)
1 def divide(x,y):
2 try:
----> 3 result = x/y
4 except ZeroDivisionError:
5 print("division by zero!")
TypeError: unsupported operand type(s) for /: 'str' and 'str'
- 最后,使用带有finally子句的异常处理结构时,应尽量避免在finally子句中使用return语句,否则可能出现出乎意料的错误
def demo_div(a,b):
try:
return a/b
except ZeroDivisionError:
print('divided by zero!')
except Exception:
print('unexpected error occured!')
finally:
return -1
demo_div(1,2)
-1
demo_div(1,0)
divided by zero!
-1
4. 断言与上下文管理
- 断言和上下文管理是一种特殊的异常处理方式,在形式上比异常处理结构要简单一些,能够满足简单的异常处理或条件确认,并且可以与标准的异常处理结构结合使用
- 在javascript中也有类似的用法
4.1 断言
断言语法:
assert expression[,reason]
当判断表达式expression为真时,什么都不做,如果表达式为False,则抛出异常
- assert语句一般用于对程序某个时刻必须满足的条件进行验证,仅当“_debug_”为True时有效
# 断言和异常处理结构经常结合使用
try:
assert 1==2,"1 is not equal 2!"
except AssertionError as reason:
print(f"{reason.__class__.__name__}:{reason}")
AssertionError:1 is not equal 2!
4.2 上下文管理
使用上下文管理语句with可以自动管理资源,在代码块执行完毕后自动还原进入该代码块之前的现场或上下文,
-
无论何种原因跳出with块,也无论什么时候发生异常,总能保证资源的正确释放,因此with上下文管理常用于文件操作,网络通信等场合
with语句的语法:with context_expr [as var]: with块
-
例如下面的代码演示了文件操作时with语句的用法,使用这样的写法程序员不用担心忘记关闭文件,当文件处理完后,将会自动关闭
with open(r'D:\sample.txt','r') as f:
for line in f:
print(line)
talk is cheap show me the code
hello world
hello world
5. 用sys模块回溯最后的异常
当发生异常时,python会回溯异常,给出大量的提示,有时候会对程序员的定位和纠错带来一定的困难,这时可以使用sys模块来回溯最近的一次异常。
语法为:
import sys try: block except: t=sys.exc_info() print(t)
- sys.exc_info()返回值是一个三元组(type,value/message,traceback)
- type:异常的类型
- value/message:异常的信息或参数
- traceback:包含调用栈信息的对象。
6. 使用PyCharm等IDE调试代码-Debug
这里不再赘述,网上教程一抓一大把,这里post一个https://blog.csdn.net/qq_33472146/article/details/90606359?depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-5&utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromBaidu-5
7. Python单元测试
- Python标准库unittest提供了大量用于单元测试的类和方法,其中TestCase类的常用方法如下:
TheTestCase
class provides several assert methods to check for and report failures. The following table lists the most commonly used methods (see the tables below for more assert methods):
Method | Checks that | New in |
---|---|---|
assertEqual(a, b) | a == b | |
assertNotEqual(a, b) | a != b | |
assertTrue(x) | bool(x) is True | |
assertFalse(x) | bool(x) is False | |
assertIs(a, b) | a is b | 3.1 |
assertIsNot(a, b) | a is not b | 3.1 |
assertIsNone(x) | x is None | 3.1 |
assertIsNotNone(x) | x is not None | 3.1 |
assertIn(a, b) | a in b | 3.1 |
assertNotIn(a, b) | a not in b | 3.1 |
assertIsInstance(a, b) | isinstance(a, b) | 3.2 |
assertNotIsInstance(a, b) | not isinstance(a, b) | 3.2 |
It is also possible to check the production of exceptions, warnings, and log messages using the following methods:
Method | Checks that | New in |
---|---|---|
assertEqual(a, b) | a == b | |
assertNotEqual(a, b) | a != b | |
assertTrue(x) | bool(x) is True | |
assertFalse(x) | bool(x) is False | |
assertIs(a, b) | a is b | 3.1 |
assertIsNot(a, b) | a is not b | 3.1 |
assertIsNone(x) | x is None | 3.1 |
assertIsNotNone(x) | x is not None | 3.1 |
assertIn(a, b) | a in b | 3.1 |
assertNotIn(a, b) | a not in b | 3.1 |
assertIsInstance(a, b) | isinstance(a, b) | 3.2 |
assertNotIsInstance(a, b) | not isinstance(a, b) | 3.2 |
There are also other methods used to perform more specific checks, such as:
Method | Checks that | New in |
---|---|---|
assertRaises(exc, fun, *args, **kwds) | fun(*args, **kwds) raises exc | |
assertRaisesRegex(exc, r, fun, *args, **kwds) | fun(*args, **kwds) raises exc and the message matches regex r | 3.1 |
assertWarns(warn, fun, *args, **kwds) | fun(*args, **kwds) raises warn | 3.2 |
assertWarnsRegex(warn, r, fun, *args, **kwds) | fun(*args, **kwds) raises warn and the message matches regex r | 3.2 |
assertLogs(logger, level) | The with block logs on logger with minimum level | 3.4 |
The list of type-specific methods automatically used by assertEqual()
are summarized in the following table. Note that it’s usually not necessary to invoke these methods directly.
Method | Checks that | New in |
---|---|---|
assertAlmostEqual(a, b) | round(a-b, 7) == 0 | |
assertNotAlmostEqual(a, b) | round(a-b, 7) != 0 | |
assertGreater(a, b) | a > b | 3.1 |
assertGreaterEqual(a, b) | a >= b | 3.1 |
assertLess(a, b) | a < b | 3.1 |
assertLessEqual(a, b) | a <= b | 3.1 |
assertRegex(s, r) | r.search(s) | 3.1 |
assertNotRegex(s, r) | not r.search(s) | 3.2 |
assertCountEqual(a, b) | a and b have the same elements in the same number, regardless of their order. | 3.2 |
Method | Used to compare | New in |
---|---|---|
assertMultiLineEqual(a, b) | strings | 3.1 |
assertSequenceEqual(a, b) | sequences | 3.1 |
assertListEqual(a, b) | lists | 3.1 |
assertTupleEqual(a, b) | tuples | 3.1 |
assertSetEqual(a, b) | sets or frozensets | 3.1 |
assertDictEqual(a, b) | dicts | 3.1 |
- 有两个很重要的方法没有记录在上表中
- setUp() 每项测试开始之前自动调用该函数--------初始化资源,连接
- tearDown() 每项测试完成之后自动调用该函数--------释放资源,连接
这两个方法常用来执行数据库的连接的创建和关闭,文件的打开和关闭等操作
测试自定义的类Stack.演示如何利用unittest库对Stack类中的入栈、出栈、改变大小以及满/空测试等方法进行测试,并将测试结果写入文件test_Stack_result.txt
import Stack
import unittest
class TestStack(unittest.TestCase):
def setUp(self):
# 测试之前以追加模式打开指定文件
self.fp = open('D:\\test_Stack_result.txt', 'a+')
# self.s = Stack.Stack()
def tearDown(self):
# 测试结束后关闭文件
self.fp.close()
def test_isEmpty(self):
try:
s = Stack.Stack()
self.assertTrue(s.is_empty()) # 断言该函数返回结果为True
self.fp.write('isEmpty passed\n')
except Exception as e:
self.fp.write('isEmpty failed\n')
def test_empty(self):
try:
s = Stack.Stack(5)
for i in ['a', 'b', 'c']:
s.push(i)
# 测试清空栈操作
s.empty()
self.assertTrue(s.is_empty())
self.fp.write('empty passed\n')
except Exception as e:
self.fp.write('empty failed\n')
def test_isfull(self):
try:
s = Stack.Stack(3)
s.push(1)
s.push(2)
s.push(3)
self.assertTrue(s.is_full())
self.fp.write('is_full passed\n')
except Exception as e:
self.fp.write('is_full failed\n')
def test_pushpop(self):
try:
s = Stack.Stack()
s.push(3)
# 确保入栈后立即出栈得到原来的元素
self.assertEqual(s.pop(), 3)
s.push('a')
self.assertEqual(s.pop(), 'a')
self.fp.write('push and pop passed\n')
except Exception as e:
self.fp.write('push and pop failed\n')
def test_set_size(self):
try:
s = Stack.Stack(6)
for i in range(6):
s.push(i)
self.assertTrue(s.is_full())
# 测试扩大栈空间是否正常工作
s.set_size(7)
s.push(6)
self.assertTrue(s.is_full())
self.assertEqual(s.pop(), 6)
# 测试缩小栈空间是否能正常工作
s.set_size(4)
self.assertTrue(s.is_full())
self.assertEqual(s.pop(), 3)
self.fp.write('set_size passed\n')
except Exception as e:
self.fp.write('set_size failed\n')
if __name__ == '__main__':
print('Provided by ChanZany')
unittest.main()
结果如下: