[Python][华为云Python编程创造营][学习笔记][11_Bug]
1,异常与错误
1.1,Bug是什么
- Bug是指在软件运行中因为程序本身有错误而造成的功能不正常、死机、数据丢失、非正常中断等现象。
1.2,Bug的由来
- Bug一词的原意是"臭虫"或"虫子"。
- 在电脑系统或程序中,如果隐藏着的一些未被发现的缺陷或问题,人们也叫它"Bug"。
- 第一代的计算机是由许多庞大且昂贵的真空管组成,并利用大量的电力来使真空管发光。引得一只小虫子钻进了一支真空管内,导致整个计算机无法工作。
研究人员费了半天时间,把这只小虫子从真空管中取出后,计算机又恢复正常。
- 与Bug相对应,人们将发现Bug并加以纠正的过程叫做"Debug",意即"捉虫子"或"杀虫子"。
1.3,程序中的Bug
Python中的Bug可以分为:
- 内建异常
编码错误:代码编写时(运行前)出现问题,程序无法运行(不可被捕获)。
异常:在运行时出现错误(比如被除数为零),程序可以运行。
- 自定义异常
- 程序逻辑错误
1.4,Python中的异常
2,异常的捕获
2.1,异常处理
2.1.1,try/except
- try:
执行代码
except:
发生异常时执行的代码
- try语句执行如下:
首先,执行try子句(在关键字try和关键字except之间的语句)。
如果没有异常发生,忽略except语句,try子句执行后结束。
如果在执行try子句的过程中发生了异常,那么try子句余下的部分将被忽略。如果异常的类型和except之后的名称相符,那么对应的except子句将被执行。
如果一个异常没有与任何的except匹配,那么这个异常将会传递到上层的try中。
- 一个try语句可能包含多个except子句,分别来处理不同的特定的异常,最多只有一个分支会被执行。
处理程序将只针对对应的try子句中的异常进行处理,而不是其他的try的处理程序中的异常,
一个except子句可以同时处理多个异常,这些异常将被放在一个括号里成为一个元组,例如:
except(RuntimeError,TypeError,NameError):
pass
最后一个except子句可以忽略异常的名称,它将被当作通配符使用。你可以使用这种方法打印一个错误信息,然后再次把异常抛出。
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
# 输出结果
# OS error: [Errno 2] No such file or directory: 'myfile.txt'
2.1.2,try/except...else
- try/except语句还有一个可选的else子句,如果使用这个子句,那么必须放在所有except子句之后,else子句将在try子句没有发生异常的时候执行。
try:
执行代码
except:
发生异常时执行的代码
esle:
没有异常时执行的代码
- 以下例子在try语句中判断文件是否可以打开,如果打开文件时正常的没有正常的发生异常则执行else部分的代码,读取文件内容
import sys
try:
f = open('C:\\Users\\abc\\Desktop\\hello.txt')
except IOError:
print("cannot open")
else:
print("Has ",len(f.readlines())," lines") # Has 4 lines
f.close()
使用else子句比把所有的语句都放在try子句里面要好,这样可以避免一些意想不到,而except又无法捕获的异常。
- 异常处理不仅仅处理那些直接发生在try子句中的异常,而且还能处理子句中调用的函数(甚至间接调用的函数)里抛出的异常。例如:
def this_fails():
x = 1/0
try:
this_fails()
except ZeroDivisionError as err:
print("Handing run-time error:",err) # Handing run-time error: division by zero
2.1.3,try-finally语句
- try-finally语句无论是否发生异常都将执行最后的代码:
try:
执行代码
except:
发生异常时执行的代码
else:
没有异常时执行的代码
finally:
不管有没异常都会执行的代码
try:
test()
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("这句话,有没异常发生都会执行")
# 输出结果
# 这句话,有没异常发生都会执行
# NameError: name 'test' is not defined
2.2,抛出异常
2.2.1,Python使用raise语句抛出一个指定的异常
- raise语法格式:raise [Exception [,args [,traceback]]]
- 以下实例如果x大于5就触发异常
x = 10
if x >5:
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!")
raise
# 输出结果
# An exception flew by!
# NameError: HiThere
2.3,用户自定义异常
- 可以通过创建一个新的异常类来拥有自己的异常。异常类继承自Exception类,可以直接继承,或者间接继承,例如:
class MyError(Exception):
def __init__(self,value):
self.value=value
def __str__(self):
return repr(self.value)
# try:
# raise MyError(2*2) # My exception occurred, value: 4
# except MyError as err:
# print("My exception occurred, value: ",err.value)
raise MyError(2*2) # __main__.MyError: 4
这个例子中,类Exception默认的__init__()被覆盖
- 当创建一个模块有可能抛出多种不同的异常时,一种通常的做法是为这个包建立一个基础异常类,然后基于这个基础类为不同的错误情况创建不同的子类:
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"结尾,就跟标准的异常命名一样。
3,程序调试
3.1,__main__
- 当写好一个模块时,要测试一下当前模块,但是又不想当前模块的测试代码被导包时加载怎么办?
- __name__:表示当前模块的名字。
当前模块为__main__。
def function():
pass
if __name__=="__main__":
function()
3.2,__name__
- 当模块被导入时
import os
os.__name__
'os'
- 当前模块下
__name__
'__main__'
3.3,程序中的print
- 在程序运行过程中产生了异常,如果对于程序中的某个节点(变量取值、逻辑执行等)不清楚其为什么会报错,如何查看?
使用print打印出来。
- 使用print来调试程序是一件效率较低的方式
程序上线时需要删除。
3.4,assert
- assert(断言),是Python中用于调试的工具,根据其后的语句选择抛出错误(False)或者继续运行(True)。
assert依赖于内置变量__debug__,当其取值为True时assert才会执行。
assert expression[,arguments]。
- assert注意事项:
不要写无用的assert。
避免用assert做逻辑和数据的验证。
程序上线时一般会禁用断言。
3.5,断点调试
- 断点(break point)是指在代码中指定位置,当程序运行到此位置时便会中断下来,并让开发者可查看此时各变量的值。因断点中断的程序并没有结束,可以选择继续执行。
断点调试需要借助IDE。
Python本身提供工具pdb,但是使用上不如IDE中的工具简单。
3.6,单元测试
- 单元测试是用来对一个模块,一个函数或者一个类进行正确性检验的测试工作。
如果单元测试通过,说明我们测试的这个函数能够正常工作。如果单元测试不通过,要么函数有Bug,要么测试条件输入不正确,总之,需要修复使单元测试能够通过。
- 这种以测试为驱动的开发模式最大好处就是确保一个程序模块的行为符合我们设计的测试用例。在将来修改的时候,可以极大程度保证该模块行为仍然正确。
3.7,unittest
- unittest是Python内置的单元测试框架,具备编写用例、组织用例、执行用例、输出报告等自动化框架的条件。
类似的工具还有pytest、nose等。
unittest在使用时需要继承unittest.TestCase类来创建一个测试用例,在这个类中,定义以test开头的方法,测试框架将把它作为独立的测试去执行。
每个用例都采用unittest内置的断言方法来判断被测对象的行为是否符合预期。
- 简单的测试用例
import unittest
class TestStringMethods(unittest.TestCase):
def test_upper(self):
self.assertEqual('foo'.upper(),'FOO')
def test_isupper(self):
self.assertTrue('FOO'.isupper())
self.assertTrue('foo'.isupper())
- unittest执行顺序
unittest.main():执行测试用例。
setUp():在每个测试方法执行之前执行。
若setUp()方法引发异常,测试框架会认为测试发生了错误,因此测试方法不会被运行。
tearDown():在每个测试方法执行之后执行。
若setUp()成功运行,无论测试方法是否成功,都会运行tearDown()。
unittest.skip(reason):无条件跳过(装饰器形式)。
setUpClass()、tearDownClass():在所有测试方法开始或结束前后执行(类方法)。
- 内置方法
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
assertIsNot(a,b) => a is not b
assertIsNone(x) => x is None
assertIsNotNone(x) => x is not None
assertIn(a,b) => a in b
assertNotIn(a,b) => a not in b
assertIsInstance(a,b) => isinstance(a,b)
assertNotIsInstance(a,b) => not isinstance(a,b)
- 点赞
- 收藏
- 关注作者
评论(0)