百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

Python实用案例编程入门第七章 调式手段

toyiye 2024-05-19 19:34 11 浏览 0 评论

本章的主题为调试手段,这是程序开发必不可少的步骤,也是占用时间最多的环节。在程序员的正常开发工作中,调试工作至少占据1/3的时间,而实际编码工作相对占用实际比较少。因此,无论您是初学者,还是编程兴趣爱好者,调试手段一定要学习。

7.1 异常信息

在前面学习过程中,我们看到过经常出现异常打印,比如像下面的代码。代码中执行了Dic.viewkeys()的方法,但是解释器给出了一个Traceback提示,字典对象dict没有viewkeys()属性。意思很直白,就是字典没有viewkeys()这样的操作,那么我们就可以去确认是否有字母写错了,或者甚至确实没有该方法。顺着这样的思路,我们就能找到问题的原因,亦或者在百度中搜索下面的错误信息,看看是否其他也遇到过类似的错误。我们需要知道,我们正在走的路一定是别人走过的,别人也肯定遇到过我们类似的问题,从而少走弯路,浪费时间。

>>> Dic.viewkeys()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'dict' object has no attribute 'viewkeys'

此外,一旦程序运行过程中遇到这种错误输出,也是在提示我们问题发生的相关信息,以帮助我们定位问题。从下面的Traceback我们可以看到,问题出在tornado-main.py文件的第6行,通过对比代码,我们就能找到出错代码行,从而解决问题。

HTTPServerRequest(protocol='http', host='127.0.0.1:8000', method='GET', uri='/', version='HTTP/1.1', remote_ip='127.0.0.1')
Traceback (most recent call last):
File "C:\Users\tornado\web.py", line 1699, in _execute
result = await result
File "C:\Users\tornado\gen.py", line 191, in wrapper
result = func(*args, **kwargs)
File "F:/05_Github/tornado/tornado-main.py", line 16, in get
raise Exception("Debug")
Exception: Debug

那么,如果我们能够提前预判,可能会存在这样那样的异常,能否通过某种方法来捕获该异常,从而让程序正确的处理遇到的问题。以免程序直接崩溃,导致使用感受差。

接下来,先通过一个代码段来了解如何捕获异常,并进行必要的处理。代码中于Redis数据库建立连接,但是并不知道是否成功,因此需要通过ping()方法进行确认。但是如果连接建立失败了,在ping()的过程中就会抛出异常,那么可以通过try,except进行捕获异常,并进行相应的后处理。比如下面代码,当出现超时错误时,我们记录一个日志打印,并返回一个None给调用者。如若是超时之外的其他错误,则全部记录日志并返回None。这样程序使用者在遇到问题的时候,就能够知道到底是什么原因导致失败,从而及时正确的解决问题。

pool = redis.ConnectionPool(host=ip, port=port, decode_responses=True)
r = redis.Redis(connection_pool=pool)
logging.debug(r)
try:
r.ping()
logging.debug('ping success')
return r
except TimeoutError:
logging.error('redis connection timeout ' + ip)
return None
except:
logging.error("redis don't exist ")
return None

所以,如果我们提前知道某段程序可能会出现异常,那么让该段程序处于try之中。下面的代码段中,当检测到异常时,进行了一些后处理操作,删除了一些临时文件,并记录日志。在下次使用的时候,程序就能够从错误中恢复正常。

user_name = None
try:
s = shelve.open('user_data', writeback=True)
user_name = s['username']
s.close()
except:
path = Path.cwd() #Path('.')
for p in path.glob(f"user_data.*"):
p.unlink()
logging.error("get_user_name, cannot open user_data to get username")

return user_name

7.2 日志调试

日志调试是一个最基本的手段,可以让我们了解程序的执行过程,从而确定程序是否按照我们的预想中运行。一般在程序调试中,我们会通过print添加一些临时打印信息,以便及时确认问题,问题确认后再将其删除。

对于一些程序运行过程中的关键信息,我们可能希望即使程序发布了也要记录。那么,logging模块是一个非常不错的选择,可以分级记录日志,并记录到日志文件中。

7.2.1 print打印

print打印应该是程序调试过程中普遍使用的方法,添加很方便,也没有依赖,并能够及时的输出到控制台上。

try:
  mail_server = smtplib.SMTP_SSL('smtp.office365.com')
  mail_server.ehlo()
  mail_server.starttls()
  mail_server.ehlo()
  mail_server.login(sender, PASS_WORD)
  mail_server.send_message(msg)
  mail_server.quit()
  print("success")

except smtplib.SMTPRecipientsRefused:
	print('邮件发送失败,收件人被拒绝')
except smtplib.SMTPAuthenticationError:
	print('邮件发送失败,认证错误')
except smtplib.SMTPSenderRefused:
	print('邮件发送失败,发件人被拒绝')
except smtplib.SMTPException:
	print('邮件发送失败, ', e.message)

7.2.2 logging模块

logging模块正如其名,是用来记录日志的,可以灵活设置帮助记录程序运行过程或辅助定位问题。支持不同的日志等级,可以很方便的设置级别,使调试版本和发布给别人的版本使用不同的日志级别。可以将日志打印在窗口,也可以保存在日志文件中。还可以自定义日志信息的格式,使其更易于阅读。

我们的程序中将定义如下日志信息格式,及具体使用的方法。通过logging.debug(),logging.info()和logging.error()等不同接口来记录日志信息。我们这里设置的level为logging.INFO级别,因此DEBUG级别的信息不会在日志中出现。当需要调试定位问题时,可以将日志级别修改为DEBUG,则所有日志信息都会被保存。

下面代码是我们程序中使用logging的代码片段。

def repair_core_dump_file(self, dump_file, core_file):
sp = [0, 0, 0, 0]
lr = [0, 0, 0, 0]
pc = [0, 0, 0, 0]

self.get_reg_from_dump_file(dump_file, 13, sp)
self.get_reg_from_dump_file(dump_file, 14, lr)
self.get_reg_from_dump_file(dump_file, 15, pc)
logging.info("sp: 0x{}".format(''.join(sp)))
logging.info("lr: 0x{}".format(''.join(lr)))
logging.info("pc: 0x{}".format(''.join(pc)))
with open(core_file, 'rb') as coreHandle:
content_by_hex = coreHandle.read().hex()
rs = content_by_hex.rfind(''.join(sp))
if rs != -1:
content_by_hex = content_by_hex.replace(''.join(sp) + '0000000000000000',
''.join(sp) + ''.join(lr) + ''.join(pc))
with open('repaired-' + core_file, 'wb') as new_coreHandle:
content_by_binary = binascii.unhexlify(content_by_hex)
new_coreHandle.write(content_by_binary)

logging.info("Position:{} is {}".format(rs, content_by_hex[rs:rs + 8]))
logging.info("Position:{} is {}".format(rs, content_by_hex[rs + 8:rs + 16]))
logging.info("Position:{} is {}".format(rs, content_by_hex[rs + 16:rs + 24]))
logging.info("content_by_hex = {}".format(content_by_hex))
logging.info("SP000 hint count: {}".format(content_by_hex.count(''.join(sp) + '0000000000000000')))
logging.info("sp hint count: {}".format(content_by_hex.count(''.join(sp))))

logging模块也支持将日志信息输出到控制台,需要进行一些必要的配置。

下面再看一段日志文件内容,我们的程序日志文件就像下面这样。

2019-09-03 15:41:42 Tue root INFO login success
2019-09-03 15:41:42 Tue root INFO current_version: v2.0
2019-09-03 15:41:43 Tue root ERROR get_user_name, cannot open user_data to get username
2019-09-03 15:41:43 Tue root ERROR get_user_name, cannot open user_data to get username
2019-09-03 15:41:43 Tue root ERROR get_user_name, cannot open user_data to get username
2019-09-03 15:42:04 Tue root INFO v2.0, maybe server don't running.
2019-09-03 15:42:23 Tue root ERROR get_user_name, cannot open user_data to get username
2019-09-03 15:42:23 Tue root ERROR get_user_name, cannot open user_data to get username
2019-09-03 15:42:23 Tue root ERROR get_user_name, cannot open user_data to get username

7.3 IDE的调试器

在我们编写Python程序的过程中,通常都会使用一定的IDE,比如Pycharm,spyder或者其他类似Python程序开发IDE。都支持集成了调试器,在调试器模式下,我们可以给程序打断点。

如果我们希望程序运行到某行的时候暂停下来,那么可以在该行设置一个断点。同时也支持单步调试,能够看到程序执行过程中每个变量的值的变化,可以说是我们调试程序的终极手段。

由于每个IDE的调试器的具体调试大同小于,这里不针对具体的IDE进行介绍。读者在使用自己的IDE时,可以根据IDE的手册进行必要的学习。

7.4 小试牛刀

在程序开发调试的过程中,可能需要多种调试手段结合着使用。但在程序发布后,我们很难通过控制台打印或者IDE的调试器进行定位问题。那么,最好的选择就是通过日志文件记录程序行为,在问题发生后我们可以通过日志文件确认问题。

如下所示,可以将日志信息记录到日志文件中。程序出现异常后,可以通过日志文件来定位问题发生的原因。

log_name = 'booking-testline.log'
logging.basicConfig(level=logging.INFO, #这里表示INFO级别以上的日志信息会被存入日志文件
format="%(asctime)s %(name)s %(levelname)s %(message)s",
datefmt='%Y-%m-%d %H:%M:%S %a',
filename=log_name,
filemode='a')

logging.debug('ping success') #DEBUG级别的日志信息是不会被存入日志文件

logging.error("redis don't exist ") #ERROR级别的日志信息会被存入日志文件

7.5 本章小结

本章介绍了一些简单的程序调试手段,也是最基本的调试手段。通过添加print打印,查看Traceback信息,记录日志文件和IDE的调试器,多种手段来调试定位程序问题。正如开题提到的,程序的调试过程将占据整个程序开发周期的大多数时间,因此这是一个必备手段。任何程序都不可能一次编写成功,因此调式是必不可少的步骤。


欢迎关注,转发,收藏

Python实用案例编程入门:第一章 Python概述及为什么学Python

Python实用案例编程入门:第二章 字符串

Python实用案例编程入门:第三章 列表和元组

Python实用案例编程入门:第四章 字典和文件

Python实用案例编程入门:第五章 函数和类

Python实用案例编程入门:第六章 控制流语句

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码