4、错误、调试、测试

try-except-finally

可以通过try-except-finally对错误进行捕获和处理

当认为某些代码可能会出错时,就可以用try来运行这段代码,如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码,即except语句块,执行完except后,如果有finally语句块,则执行finally语句块,至此,执行完毕。

try:
    a = 1 / 0
# 处理除零错误
except ZeroDivisionError as e:
    print(e)
finally:
    print('is finally')

'''
division by zero
is finally
'''

处理多个错误

try:
    a = 1 / int('a')
# 处理除零错误
except ZeroDivisionError as e:
    print(e)
# 处理参数错误
except ValueError as e:
    print(e)
finally:
    print('is finally')

'''
invalid literal for int() with base 10: 'a'
is finally
'''

可以在except语句块后面添加else语句块,如果没有错误发生,将执行else语句块

try:
    a = 1 / int('1')
# 处理除零错误
except ZeroDivisionError as e:
    print(e)
# 处理参数错误
except ValueError as e:
    print(e)
else:
    print('is else')
finally:
    print('is finally')

'''
is else
is finally
'''

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
           +-- EncodingWarning
           +-- ResourceWarning

记录错误

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

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

import logging

try:
    a = 1 / 0
except BaseException as e:
    print(e)
    logging.exception(e)
finally:
    print('is finally')

'''
division by zero
ERROR:root:division by zero
Traceback (most recent call last):
  File "d:\python_project\demo\main.py", line 4, in <module>
    a = 1 / 0
ZeroDivisionError: division by zero
is finally
'''

logging错误级别

CRITICAL > ERROR > WARNING > INFO > DEBUG

debug : 打印全部的日志,详细的信息,通常只出现在诊断问题上

info : 打印info,warning,error,critical级别的日志,确认一切按预期运行

warning : 打印warning,error,critical级别的日志,一个迹象表明,一些意想不到的事情发生了,或表明一些问题在不久的将来(例如。磁盘空间低”),这个软件还能按预期工作

error : 打印error,critical级别的日志,更严重的问题,软件没能执行一些功能,exception属于error,但是会显示堆栈

critical : 打印critical级别,一个严重的错误,这表明程序本身可能无法继续运行

常用api

Logging.Formatter:这个类配置了日志的格式,在里面自定义设置日期和时间,输出日志的时候将会按照设置的格式显示内容。
Logging.Logger:Logger是Logging模块的主体,进行以下三项工作:
1. 为程序提供记录日志的接口
2. 判断日志所处级别,并判断是否要过滤
3. 根据其日志级别将该条日志分发给不同handler
常用函数有:
Logger.setLevel() 设置日志级别
Logger.addHandler() 和 Logger.removeHandler() 添加和删除一个Handler
Logger.addFilter() 添加一个Filter,过滤作用
Logging.Handler:Handler基于日志级别对日志进行分发,如设置为WARNING级别的Handler只会处理WARNING及以上级别的日志。
常用函数有:
setLevel() 设置级别
setFormatter() 设置Formatter

输出日志到文件

import logging

#创建一个logger
logger = logging.getLogger()
#Log等级总开关
logger.setLevel(logging.INFO)
# 创建一个handler,用于写入日志文件,mode='w'表示程序重启重写日志
fh = logging.FileHandler('./logging.log',mode='w')
# 定义handler的输出格式
formatter = logging.Formatter("%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s")
fh.setFormatter(formatter)
# 输出到file的log等级的开关
fh.setLevel(logging.ERROR)
logger.addHandler(fh)

try:
    a = 1 / 0
except BaseException as e:
    print(e)
    logging.exception(e)
finally:
    print('is finally')

'''
logging.py文件:
2022-02-14 21:11:28,643 - main.py[line:20] - ERROR: division by zero
Traceback (most recent call last):
  File "d:\python_project\demo\main.py", line 17, in <module>
    a = 1 / 0
ZeroDivisionError: division by zero
'''

format常用格式

%(levelno)s: 打印日志级别的数值
%(levelname)s: 打印日志级别名称
%(pathname)s: 打印当前执行程序的路径,其实就是sys.argv[0]
%(filename)s: 打印当前执行程序名
%(funcName)s: 打印日志的当前函数
%(lineno)d: 打印日志的当前行号
%(asctime)s: 打印日志的时间
%(thread)d: 打印线程ID
%(threadName)s: 打印线程名称
%(process)d: 打印进程ID
%(message)s: 打印日志信息

自定义错误、抛出错误

错误也是一个class,所以需要继承一个合适的错误类

使用raise可以抛出一个错误

class NameError(ValueError):
    pass

def setName(name):
    if(len(name) > 4):
        raise NameError('名称不合法:%s' % name)

setName('一二三四五')

'''
Traceback (most recent call last):
  File "d:\python_project\demo\main.py", line 11, in <module>
    setName('一二三四五')
  File "d:\python_project\demo\main.py", line 9, in setName
    raise NameError('名称不合法:%s' % name)
__main__.NameError: 名称不合法:一二三四五
'''