7、Python 异常处理、pdb调试&logging

一、异常处理(你不可能总是对的)

1. 异常处理机制的重要性

  • 增强程序的健壮性和用户体验,尽可能的捕获所有预知的异常并写好处理的代码,当异常出现的时候,程序自动消化并恢复正常(不至于崩溃)。

2. 常见异常

  • AssertionError:断言语句失败
  • AttributeError:尝试访问未知的对象属性
  • ImportError:导入模块失败
  • OSError:操作系统产生的异常
  • SyntaxError:语法错误
  • TypeError:不同类型间的无效操作
  • ValueError:传入无效的参数
  • IOError:输入/输出异常;基本上是无法打开文件

3. 异常处理办法

try:						# 当我们认为某些代码可能会出错时。若执行出错,                                                
	检测范围                 # 则后续代码不会继续执行,而是直接跳至错误处理代码,即except语句块。
	                  
except Exception(上面的常见异常) as e:  # 当无法预知会发生哪些异常,可以去掉括号及其中内容去捕获异常原因 e 
	print('出错啦! \n 错误的原因是:' + str(e))
	出现异常后的处理代码(pass:不处理), eg:
		print('出错图片的路径:', img_path)
		wrong_cnt += 1
		continue  # 进入下一次循环
	
finally:
	无论如何都会被执行的代码(通常是为了实现不得不执行的收尾工作---比如在程序崩溃前保存用户文档)		

4. assert(断言)

  • 当这个关键字后边的条件为假的时候,程序会自动崩溃并抛出AssertionError的异常
  • 一般来说,我们可以在程序中置入检查点,当需要确保程序中的某个条件一定为真才能让程序正常工作的话,assert关键字就非常有用了

二、Python Debug 工具

1. pdb 调试

调试步骤

# 在需要调试的文件中:import pdb
import pdb

# 在可能出错的地方设置断点:pdb.set_trace()
pdb.set_trace()

pdb 命令详解

# 只需输入括号中的命令即可
list(l):查看当前代码段
next(n):单步调试,不进入函数内部(step over)
step(s):进入函数内部(step into)

查看变量的值:p 变量名/大多数情况可以直接输入变量名查看/查看多个变量用逗号隔开即可
动态设置断点:b+需要设置断点的行号/单打一个b:查看所有的断点编号/cl 编号:清除编号处断点/cl:清除所有断点

return(r):执行代码直到从当前函数返回
continue(c):继续执行程序
jump(j):j+行号,让程序跳转到指定行号
args(a):打印当前函数的实参值

quit(q):终止并退出

2. 使用 logging 模块记录日志

A、logging 的日志类型及级别顺序

  • 日志类型
    • logging.critical(msg)
    • logging.error(msg)
    • logging.warning(msg)
    • logging.info(msg)
    • logging.debug(msg)
  • 级别排序及好处
    • 排序:CRITICAL > ERROR > WARNING > INFO > DEBUG > NOTSET,默认情况下,logging 的日志级别为 WARNING,只有不低于 WARNING 级别的日志才会显示在命令行
    • 好处:在项目开发时 debug 用的 log,在产品 release 阶段不用一一注释,只需要调整 logger 的级别就可以了,比 print 函数方便很多

B、logging 模块构成

I、Logger 记录器:暴露了应用程序代码能直接使用的接口

  • 创建一个记录器 实例 name,将其赋值给 logger
    • logger = logging.getLogger(name)
  • 记录器的配置方法
    • logger.setLevel(WARNING):指明了记录器能处理的日志消息的最低级别
    • logger.addHandler()logger.removeHandler():在记录器对象上添加或者移除处理器对象
  • 记录日志消息的方法
    • 记录各个级别的日志:logger.debug(msg),logger.info(msg),logger.warning(msg),logger.error(msg)和logger.critical(msg)
    • 日志消息(msg):实际上是个格式化字符串

II、Handler 处理器:将(记录器产生的)日志记录发送至合适的目的地

  • 常用的处理器
    • FileHandler instances:send error messages to disk files.
    • StreamHandler instances:send error messages to streams (file-like objects).
  • 处理器配置方法
    • setLevel():指明了处理器将会分发日志的最低级别
    • setFormatter():为该处理器选择一个格式化器
  • 为什么会有两个 setLevel() 方法?
    • 记录器的级别决定了消息是否要传递给处理器
    • 每个处理器的级别决定了消息是否要分发
  • 记录器可以通过 addHandler() 方法给它自己添加零个或多个处理器对象
    • 例如:一个应用可以将所有的日志消息发送至日志文件,所有的错误(error)及其以上的日志消息发送至标准输出,所有的严重的(critical)日志消息发送至某个电子邮箱

III、Formatter 格式化器:指明了最终输出中日志记录的布局

  • logging.Formatter.(fmt=None, datefmt=None)
  • 格式化器的两个可选的参数
    • 消息格式化字符串(fmt): 指定输出的格式和内容
      • %(asctime)s: 打印日志的时间
      • %(levelname)s: 打印日志级别名称
      • %(filename)s: 打印当前执行程序名
      • %(lineno)d: 打印日志的当前行号
      • %(message)s: 打印日志信息
    • 日期格式化字符串(datefmt):指定时间格式
      • %a 星期几的简写
      • %d 十进制表示的每月的第几天
      • %b 月分的简写

C、代码实践

I、初始化 logger 配置并定义 log 方法

# coding=utf8
# log.py
import logging

class logger:
    # 初始化 logger 配置 
	def __init__(self, file_path):
        # 创建一个 logger 实例,然后设置总的日志最低输出等级
        self.logger = logging.getLogger(file_path)
        self.logger.setLevel(logging.DEBUG)
        
        # 创建一个 handler,用于写入日志文件,然后设置文件日志最低输出等级 
        self.fh = logging.FileHandler(file_path) # 默认是追加的方式'a'
        self.fh.setLevel(logging.DEBUG)
         
        # 再创建一个 handler,用于输出到控制台,然后设置控制台日志最低输出等级
        self.ch = logging.StreamHandler()
        self.ch.setLevel(logging.DEBUG)
        
        # 使用格式化器定义 handler 的输出格式,-24 代表 asctime 输出宽度为 24 bytes,左对齐
        self.fmt = "%(asctime)-24s %(levelname)s %(filename)s %(lineno)d %(message)s"
        self.datefmt = "%a %d %b %Y %H:%M:%S"
        self.formatter = logging.Formatter(self.fmt, self.datefmt)  
        self.fh.setFormatter(self.formatter)
        self.ch.setFormatter(self.formatter)
        
        # 给 logger 添加 handler 
        self.logger.addHandler(self.fh)
        self.logger.addHandler(self.ch)

    # 记录日志 
    def log(self, msg):
        self.logger.info(msg)	

II、记录日志

def get_logger():
    local_time = time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime())
    log_path = 'log/' + local_time + '.log' # 使用本地时间作为 log 文件名 
	
    log_ins = log.logger(log_path)  # 创建一个 log 模块的 logger 类的实例对
    log_ins.log('Begin logging...') # 然后调用 log 模块中的 log 方法去记录日志
    return log_ins  

# 取得日志模块的实例对象
log_ins = get_logger() 

# 然后调用 log 模块中的 log 方法去记录日志
info = 'Iteration: [{:6d}/{:6d}] train_loss: {:.6f}, test_loss: {:.6f}, test_acc: {:.4f}'.format(itr, FLAGS.iteration, train_loss, test_loss, test_acc)
log_ins.log(info)  

三、参考资料

1、logging-advanced-tutorial
2、python logging模块使用教程

你可能感兴趣的:(Python)