如何调试python代码,python调试程序的方法

  如何调试python代码,python调试程序的方法

  Yyds干货库存

  即使你写出了清晰易读的代码,即使你用测试覆盖了代码,即使是非常有经验的开发人员也不可避免地会有奇怪的bug,你需要用某种方式来调试它们。很多人只是用一堆打印语句来看看代码里到底是怎么回事。这种方法很不理想,有更好的方法来找出代码错误。我们将在本文中探讨其中的一些。

  录音是必须的。如果在写应用的时候没有某种日志设置,最终会后悔的。如果应用程序中没有日志,就很难排除任何错误。幸运的是,在Python中,设置一个基本的记录器非常简单:

  导入日志记录

  logging.basicConfig(

  文件名=application.log ,

  级别=记录。警告,

  format=[%(asctime)s] {%(路径名)s:%(线路号)d} %(级别名)s - %(消息)s ,

  datefmt=%H:%M:%S

  )

  logging.error(发生了一些严重的错误。)

  Logging.warning(您正在使用的函数已被否决。)这就是开始将日志写入文件所需的全部内容,看起来会像下面这样(可以使用logging.getlogger类()。root.handlers [0]。basefilename来查找文件路径):

  [12:52:35] { stdin :1}错误-出现了一些严重的错误。

  [12: 52: 35] {stdin: 1}警告-您正在使用的函数已被否决。这个设置似乎足够好了(通常),但是一个配置良好、格式良好且可读的日志可以让您的生活更轻松。改进和扩展配置的一种方法是使用。ini或者。由记录器读取的yaml文件。例如,您可以在配置中执行以下操作:

  版本:1

  禁用现有记录器:真

  格式化程序:

  标准:

  格式:“[%(asctime)s] {%(路径名)s:%(线路号)d} %(级别名)s - %(消息)s”

  日期: %H:%M:%S

  经手人:

  控制台:#将登录到标准输出的处理程序

  类:日志记录。流处理器

  级别:调试

  格式化程序:标准#使用上面定义的格式化程序

  stream: ext://sys.stdout

  file: #将记录到文件中的处理程序

  class:logging . handlers . rotating file handler

  级别:警告

  格式化程序:标准#使用上面定义的格式化程序

  文件名:/tmp/warnings.log

  最大字节数:10485760 # 10MB

  备份计数:10

  编码:utf8

  root: # Loggers是按层次结构组织的——这是root logger配置

  级别:错误

  处理程序:[控制台,文件] #附加上面定义的两个处理程序

  记录器:#定义根记录器的后代

  我的模块:“我的模块”的# Logger

  级别:信息

  处理程序:[file] #将只使用上面定义的“file”处理程序

  Propagate: No #不会将日志传播到“root”日志记录器。在python代码中具有如此广泛的配置将很难导航、编辑和维护。将内容保存在YAML文件中,可以更容易地设置和调整多个具有特定设置的记录器,例如上述设置。

  如果你想知道所有这些配置字段的来源,这些在官方文档中都有记录,大部分只是关键字参数,如第一个例子所示。

  因此,现在文件中有一个配置,这意味着我们需要以某种方式加载它。最简单的方法是使用YAML文件:

  导入yaml

  从日志导入配置

  用open(config.yaml , rt )作为f:

  config _ data=YAML . safe _ load(f . read()

  Python Logger实际上并不直接支持YAML文件,但它支持字典配置,如果您倾向于使用旧的,可以通过使用yaml.safe_load从YAML轻松创建字典配置。ini文件,那么我只想指出,根据官方文档,使用字典配置是新应用的推荐方法。更多例子请查看官方日志手册。

  日志装饰器延续了以前的日志技术,您可能会遇到需要记录一些错误的函数调用的情况。除了修改函数的主体,您还可以使用一个日志装饰器,它会用特定的日志级别和可选消息记录每个函数调用。让我们来看看装潢师:

  从functools导入包装,部分

  导入日志记录

  def attach_wrapper(obj,func=无):# Helper函数,将函数附加为对象的属性

  如果功能为无:

  返回部分(附加包装器,对象)

  setattr(obj,func .__name__,func)

  返回功能

  定义日志(级别,消息):#实际装饰者

  定义装饰(功能):

  logger=logging.getLogger(func .__模块__) #安装记录器

  格式化程序=记录。格式化程序(

  %(ASC时间)s-%(名称)s-%(级别名称)s-%(消息)s )

  处理程序=日志记录StreamHandler()

  handler.setFormatter(格式化程序)

  logger.addHandler(处理程序)

  log _ message=f"{ func .__name__} - {message}

  @wraps(func)

  def包装(*args,**kwargs): #在执行修饰函数之前记录消息

  logger.log(级别,日志消息)

  return func(*args,**kwargs)

  @attach_wrapper(wrapper) #将"设置级别"作为属性附加到"包装"

  定义设置级别(新级别):#允许我们设置日志级别的函数

  非局域能级

  级别=新级别

  @attach_wrapper(wrapper) #将"设置消息"作为属性附加到"包装"

  定义设置消息(新消息):#允许我们设置消息的函数

  非本地日志消息

  log _ message=f"{ func ._ _ name _ _ }-{新消息}

  返回包装

  返回装饰

  #用法示例

  @log(日志记录。警告,示例-参数)

  def somefunc(args):

  返回参数

  somefunc(“一些参数")

  somefunc.set_level(日志记录关键)#通过访问内部装饰函数来更改日志级别

  一些功能。set _ message( new-message )#通过访问内部装饰函数更改日志消息

  somefunc(“一些参数")毋庸置疑,这可能需要一点时间才能让你的头脑清醒(你可能只想复制粘贴并使用它)。这里的想法是,日志函数接受参数,并将其提供给内部包装材料函数。然后,通过添加附加到装饰器的访问器函数,使这些参数可调整。至于functools.wraps装饰器——若我们在这里不使用它,函数的名称(功能.__name__)将被装饰器的名称覆盖。但这是个问题,因为我们想打印名字。这可以通过functools.wraps将函数名、文档字符串和参数列表复制到装饰器函数来解决。

  无论如何,这是上面代码的输出。很整洁,对吧?

  2020-05-01 14:42:10,289-_ _ main _ _-警告-一些函数-示例-参数

  2020-05-01 14:42:10,289-_ _ main _ _-CRITICAL-some func-new-message _ _ repr _ _更多可读日志对代码的简单改进使其更易于调试,就是在类中添加__repr__方法。若你不熟悉这个方法,它所做的只是返回类实例的字符串表示。使用__repr__方法的最佳实践是输出可用于重新创建实例的文本。例如:

  班级圈:

  def __init__(self,x,y,radius):

  self.x=x

  self.y=y

  自身半径=半径

  def __repr__(self):

  返回f 矩形({self.x},{self.y},{self.radius})

  .

  c=圆(100,80,30)

  回复(三)

  #圆圈(100,80,30)如果如上所示表示对象是不可取的或不可能的,那么好的替代方法是使用.表示,例如_io .text io wrapper name= some file。 txt mode= w encoding= UTF-8 。

  除了__repr__之外,实现__str__方法也是一个好主意,默认情况下,在调用打印(实例)时使用该方法。使用这两种方法,只需打印变量即可获得大量信息。

  __缺少_ _字典的邓德方法如果你出于任何原因需要实现自定义字典类,那么当你尝试访问一些实际上不存在的键时,可能会因为键盘错误而出现一些臭虫。为了避免不得不在代码中四处查看缺少的键,可以实现特殊的__缺少_ _方法,每次引发键盘错误时都会调用该方法。

  在线类(字典):

  def __missing__(self,key):

  message=f { key }在字典中不存在!

  日志记录.警告(消息)

  Message # or raise some error相反,上面的实现非常简单,只返回并记录缺少键的消息,但是您也可以记录其他有价值的信息,以便为您提供有关代码中错误的更多上下文。

  调试崩溃的应用程序如果您的应用程序在您有机会看到发生了什么之前就崩溃了,您可能会发现这种技术非常有用。

  -i用参数-i (python3 -i app.py)运行应用程序会导致它在程序退出后立即启动交互式shell。此时,您可以检查变量和函数。

  如果这还不够好,可以使用更大的hammer- pdb -Python调试器。Pdb有相当多的特性,可以保证文章的独立性。但是这里有一个例子和最重要部分的总结。让我们先看看我们的小崩溃脚本:

  #崩溃_应用程序. py

  SOME_VAR=42

  类SomeError(异常):

  及格

  定义函数():

  引发一些错误(出了问题.)

  Func()现在,如果我们用-i参数运行它,我们就有机会调试它:

  #运行崩溃应用程序

  ~ $ python3 -i崩溃_app.py

  回溯(最近一次呼叫):

  模块中第9行的文件“crashing_app.py”

  函数()

  func中文件 crashing_app.py 的第7行

  引发一些错误(出了问题.)

  __main__。SomeError:出错了.

  #我们是互动的贝壳

  导入pdb

  pdb.pm() #启动事后检查调试器

  ./crashing_app.py(7)func()

  -引发某个错误(出错了.)

  (Pdb) #现在我们处于调试器中,可以四处查看并运行一些命令:

  (Pdb) p SOME_VAR #打印变量的值

  四十二个

  (Pdb) l #列出我们正在处理的代码

  2

  3类SomeError(异常):

  4次通过

  五

  6定义函数():

  7 -引发一些错误(出了问题.)

  八

  9函数()

  [EOF]

  (pdb) #持续调试.设置断点、单步执行代码等。上面的调试会话显示了如何非常简单地使用PDB。程序终止后,我们进入交互式调试会话。首先,我们导入pdb并启动调试器。此时,我们可以使用所有的pdb命令。如上例,我们使用p命令打印变量,使用l命令打印列表代码。在大多数情况下,您可能希望设置断点。您可以使用b LINE_NO来设置断点并运行程序,直到到达断点(c),然后继续使用s来逐句通过函数,或者您可以使用w来打印堆栈跟踪。要获得完整的命令列表,您可以查阅官方pdb文档。

  检查堆栈跟踪。例如,假设您的代码是运行在远程服务器上的Flask或Django应用程序,您无法在其中获得交互式调试会话。在这种情况下,您可以使用traceback和sys包来了解代码中的错误:

  导入追溯

  导入系统

  定义函数():

  尝试:

  引发一些错误(出了问题.)

  除了:

  当trace back . print _ exc(file=sys . stderr)运行时,上面的代码将打印最后一个抛出的异常。除了打印,还可以打印堆栈跟踪(traceback.print_stack())或者提取原始堆栈帧,格式化后进一步检查(trace back . format _ list(trace back . extract _ stack())。

  调试期间重新加载模块。有时,您可能正在调试或试验交互式shell中的一些功能,并对它们进行频繁的更改。为了使运行/测试和修改的循环更容易,您可以运行importlib.reload(module)以避免在每次更改后重新启动交互式会话:

  从模块导入函数

  函数()

  这就是结果.

  #对“func”进行一些更改

  函数()

  这就是结果.#过时的结果

  从importlib导入重新加载;reload(module) #在对“func”进行更改后重新加载“module”

  函数()

  “新结果……”的技巧更多的是关于效率而不是调试。能够跳过一些不必要的步骤,让你的工作流程更快更有效率,这总是很好的。通常,不时地重新加载模块是一个好主意,因为它可以帮助您避免调试同时被修改多次的代码。

  调试是一门艺术。

  结论大多数情况下,编程的真正意义只是大量的试错。另一方面,在我看来,调试是一门艺术,需要时间和经验去掌握。对所用的库或框架了解得越多,就越容易。上面列出的提示和技巧可以使调试更高效、更快速,但是除了这些特定于Python的工具之外,您可能还需要熟悉一般的调试方法。

  原创作品来自程,

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: