即使您编写了干净可读的代码,即使您是一个非常有经验的开发人员,也不可避免地会出现奇怪的错误,您将需要以某种方式调试它们。很多人使用一堆打印语句来查看他们的代码中发生了什么。这种方法远非理想,但有更好的方法可以找出您的代码有什么问题,本文探讨了其中的一些问题以及如何处理它们。日志记录是必须的如果你在编写应用程序时没有设置日志记录,你最终会后悔的。应用程序中没有任何日志使得故障排除非常困难。幸运的是,在Python中,设置一个基本的日志记录程序非常简单:%(lineno)d}%(levelname)s-%(message)s',datefmt='%H:%M:%S')logging.error("Someseriouseroroccurred.")logging.warning('Functionyouareusingisdeprecated.')这就是开始记录文件所需的全部内容,它看起来像这样,您可以使用logger.getloggerclass().root.handlers[0].baseFilename找到文件的路径:[12:52:35]{:1}ERROR-Someseriouseroroccurred.[12:52:35]{:1}警告-您正在使用的功能已弃用。这个设置似乎足够好(通常是),但是如果配置良好,格式化的、可读的日志可以让你的工作更容易。改进和扩展配置的一种方法是使用记录器读取的.ini或.yaml文件。例如,您可以在配置中执行的操作:version:1disable_existing_loggers:trueformatters:standard:format:"[%(asctime)s]{%(pathname)s:%(lineno)d}%(levelname)s-%(message)s"datefmt:'%H:%M:%S'handlers:console:#handlerwhichwhichwhichwilllogintofileclass:logging.handlers.RotaveltingFileHandler:WARNINGformatter:standard#Useformatterdefinedabovefilename:/tmp/warnings.logmaxBytes:10485760#10MBbackupCount:10encoding:utf8root:#Loggersareorganizedinhierarchy-thisistherootloggerconfiglevel:ERRORhandlers:[console,file]#Attachesbothhandlerdefinedaboveloggers:#Definesdescendantsofrootloggermymodule:#Loggerfor"mymodule"level:INFOhandlers:[file]#Willonlyuse"file"handlerdefinedabovepropagate:no#Willnotpropagatelogsto"root"记录器使用此扩展配置在python代码中将难以导航、编辑和维护。将内容保存在YAML文件中可以更轻松地使用非常具体的设置(如上面的设置)设置和调整多个记录器。在文件中有配置意味着我们需要加载它。使用YAML文件最简单的方法:importyamlfromloggingimportconfigwithopen("config.yaml",'rt')asf:config_data=yaml.safe_load(f.read())config.dictConfig(config_data)Python记录器实际上并不支持YAML直接文件,但它支持字典配置,可以使用YAML.safe_load从YAML轻松创建。如果您更倾向于使用旧的.ini文件,那么我只想指出,对于新的应用程序,根据文档,建议使用字典配置。__repr__可读日志要对代码进行简单改进以使其更易于调试,您可以向类中添加__repr__方法。如果您不熟悉此方法-它所做的只是返回类实例的字符串表示形式。最佳做法是使用__repr__方法输出可用于重新创建实例的文本。例如:classCircle:def__init__(self,x,y,radius):self.x=xself.y=yself.radius=radiusdef__repr__(self):returnf"Rectangle({self.x},{self.y},{self.radius})"...c=Circle(100,80,30)repr(c)#Circle(100,80,30)除了__repr__,在调用print(实例)时,__str__方法的执行也是个好主意。使用这两种方法,您可以通过打印变量来获取大量信息。字典的__missing__方法如果由于某种原因你需要实现一个自定义字典类,当你试图访问一些实际上不存在的键时,你可能会遇到一些由KeyError引起的错误。为避免在代码中四处查看缺少哪个键,您可以实现每次引发KeyError时都会调用的特殊__miss__方法。classMyDict(dict):def__missing__(self,key):message=f'{key}notpresentinthedictionary!'logging.warning(message)returnmessage#Orraisesomeerrorinstead上面的实现非常简单,只返回和记录丢失键的消息,但你可以还记录其他有价值的信息以了解代码中出了什么问题。调试崩溃的应用程序如果您的应用程序在您有机会了解其中发生的事情之前就崩溃了,您可能会发现这个技巧很有用。使用-i参数运行应用程序(python3-iapp.py)会导致交互式shell在程序退出后立即启动。此时,您可以检查变量和函数。如果这还不够好,你可以带上一个更强大的工具——pdb——Python调试器。pdb有很多特性,可以单独写一篇文章来解释。但这里有一个示例和最重要部分的概要。让我们先看看崩溃脚本:#crashing_app.pySOME_VAR=42classSomeError(Exception):passdeffunc():raiseSomeError("Somethingwentwrong...")func()现在,如果我们使用-i参数运行它,我们有调试它的机会:#Runcrashingapplication~$python3-icrashing_app.pyTraceback(mostrecentcalllast):File"crashing_app.py",line9,infunc()File"crashing_app.py",line7,infuncraiseSomeError("Somethingwentwrong...")__main__.SomeError:出错了...>>>#Weareinteractiveshell>>>importpdb>>>pdb.pm()#startPost-Mortemdebugger>.../crashing_app.py(7)func()->raiseSomeError("出了点问题...")(Pdb)#Nowweareindebuggerandcanpokearoundandrunsomecommands:(Pdb)pSOME_VAR#Printvalueofvariable42(Pdb)l#Listsurroundingcodeweareworkingwith23classSomeError(Exception):4pass56deffunc():7->raiseSomeError("Somethingwentwrong...")89Efunc(#Continuedebugging...setbreakpoints,stepproughthecode,etc.上面的调试会话非常简单地展示了你可以用pdb做什么。程序结束后,我们进入交互式调试会话。首先,导入pdb并启动调试器。至此,我们就可以使用所有的pdb命令了。如上例,我们使用p命令打印变量,使用l命令列出代码。大多数时候你可能想要设置断点,这可以通过bLINE_NO来完成并运行程序直到断点(c),然后继续使用s,可能使用w来翻页函数的选项。堆栈跟踪假定您的代码是在远程服务器上运行的Flask或Django应用程序,您无法在其中获得交互式调试会话。在这种情况下,您可以使用traceback和sys包来了解它在您的代码中失败的位置:)在运行时,上面的代码会打印最后抛出的异常。除了打印异常之外,您还可以使用traceback包打印堆栈跟踪(traceback.print_stack())或提取原始堆栈帧,对其进行格式化并进一步检查(traceback.format_list(traceback.extract_stack()))。在调试期间重新加载模块有时,您可能会在交互式shell中调试或试验某些功能,并经常更改它们。为了使运行/测试和修改的循环更容易,您可以运行importlib.reload(module)以避免在每次更改后重新启动交互式会话:>>>importfuncfrommodule>>>func()"Thisisresult..."#Makesomechangesto"func">>>func()"Thisisresult..."#Outdatedresult>>>fromimportlibimportreload;reload(module)#Reload"module"afterchangesmadeto"func">>>func()"Newresult..."这个技巧是更多关于效率而不是调试。能够跳过一些不必要的步骤并使您的工作流程更快、更高效总是很好的。通常,不时重新加载模块是个好主意,因为它可以帮助您避免调试已多次修改的代码。