深入理解Python异常处理的哲学
所谓异常指的是程序的执行出现了非预期行为,就好比现实中的做一件事过程中总会出现一些意外的事。异常的处理是跨越编程语言的,和具体的编程细节相比,程序执行异常的处理更像是哲学。限于认知能力和经验所限,不可能达到像解释器下importthis看到的python设计之禅一样,本文就结合实际使用简单的聊一聊。
0.前言
工作中,程序员之间一言不合就亮代码,毕竟不管是代码本身还是其执行过程,不会存在二义性,更不会含糊不清,代码可谓是程序员之间的官方语言。但是其处理问题的逻辑或者算法则并非如此。
让我至今记忆犹新的两次程序员论剑有:
反问一:项目后期所有的异常处理都要去掉,不允许上线后出现未知的异常,把你这里的异常处理去掉,换成ifelse;
反问二:这里为什么要进行异常处理?代码都是你写的,怎么会出现异常呢?
这是我亲身经历的,不知道大家碰到这两个问题会怎样回答,至少我当时竟无言以对。这两个问题分别在不同的时间针对不同的问题出自一个互联网巨头中某个资深QA和资深开发的反问。
暂且不论对错,毕竟不同人考虑问题的出发点是不同的。但是从这么坚决的去异常处理的回答中至少有一点可以肯定,那就是很多人对自己的代码太过自信或者说是察觉代码潜在问题的直觉力不够,更别提正确的处理潜在的问题以保证重要业务逻辑的处理流程。写代码的时候如果只简单考虑正常的情况,那是在往代码中下毒。
接下类本篇博文将按照套路出牌(避免被Ctrl+W),介绍一下python的异常处理的概念和具体操作.
1.为什么要异常处理
常见的程序bug无非就两大类:
- 语法错误;
- 逻辑不严谨或者思维混乱导致的逻辑错误;
显然第二种错误更难被发现,且后果往往更严重。无论哪一种bug,有两种后果等着我们:一、程序崩掉;二、执行结果不符合预期;
对于一些重要关键的执行操作,异常处理可以控制程序在可控的范围执行,当然前提是正确的处理。
比如我们给第三方提供的API或者使用第三方提供的API。多数情况下要正确的处理调用者错误的调用参数和返回异常结果的情况,不然就可能要背黑锅了。
在不可控的环境中运行程序,异常处理是必须的。然而困难的地方是当异常发生时,如何进行处理。
2.python异常处理
下面逐步介绍一下python异常处理相关的概念。
2.1异常处理结构
必要的结构为try...except,至少有一个except,else和finally可选。
try: codeblocks except(ExceptionClass1,ExceptionClass2,...)ase: catchandprocessexception exceptExceptionClassN: catchandprocessexception ...... else: whennothingunexpectedhappened finally: alwaysexecutedwhenalltoend
2.2python内置异常类型
模块exceptions中包含了所有内置异常类型,类型的继承关系如下:
BaseException +--SystemExit +--KeyboardInterrupt +--GeneratorExit +--Exception +--StopIteration +--StandardError |+--BufferError |+--ArithmeticError ||+--FloatingPointError ||+--OverflowError ||+--ZeroDivisionError |+--AssertionError |+--AttributeError |+--EnvironmentError ||+--IOError ||+--OSError ||+--WindowsError(Windows) ||+--VMSError(VMS) |+--EOFError |+--ImportError |+--LookupError ||+--IndexError ||+--KeyError |+--MemoryError |+--NameError ||+--UnboundLocalError |+--ReferenceError |+--RuntimeError ||+--NotImplementedError |+--SyntaxError ||+--IndentationError ||+--TabError |+--SystemError |+--TypeError |+--ValueError |+--UnicodeError |+--UnicodeDecodeError |+--UnicodeEncodeError |+--UnicodeTranslateError +--Warning +--DeprecationWarning +--PendingDeprecationWarning +--RuntimeWarning +--SyntaxWarning +--UserWarning +--FutureWarning +--ImportWarning +--UnicodeWarning +--BytesWarning
2.3exceptclause
excpet子句的常用的写法如下:
- except:#默认捕获所有类型的异常
- exceptExceptionClass:#捕获ExceptionClass类型的异常
- exceptExceptionClassase:#捕获ExceptionClass类型的异常,异常对象赋值到e
- except(ExceptionClass1,ExceptionClass2,...)ase:#捕获列表中任意一种异常类型
上面的异常类可以是下面python内置异常类型,也可以是自定义的异常类型。
2.4异常匹配原则
- 所有except子句按顺序一一匹配,匹配成功则忽略后续的except子句;
- 若抛出异常对象为except子句中给出的异常类型的对象或给出的异常类型的派生类对象,则匹配成功;
- 如果所有的except子句均匹配失败,异常会向上传递;
- 如果依然没有被任何try...except捕获到,程序在终止前会调用sys.excepthook进行处理;
2.5else&finally
如果没有异常发生,且存在else子句,则执行else子句。只要存在finally子句,无论任何情况下都会被执行。
可能唯一不好理解的地方就是finally。没有异常、捕获异常、异常上传以及异常处理过程中发生异常等均会执行finally语句。
下面看个例子:
defdivision(a,b): try: print'res=%s'%(a/b) except(ZeroDivisionError,ArithmeticError)ase: returnstr(e)#注意此处使用的是return else: print'%s/%s=%s'%(a,b,a/b) finally: print'finallyclause'
分别输入参数(1,2),(1,0)和(1,“0”)执行:
print'returnvalue:%s'%division(a,b)
得到的结果如下:
res=0
/2=0
finallyclause
returnvalue:Nonefinallyclause
returnvalue:integerdivisionormodulobyzerofinallyclause
Traceback(mostrecentcalllast):
File"D:\MyFolders\Cnblogs\AlphaPanda\Main.py",line217,in
print'returnvalue:%s'%division(1,"0")
File"D:\MyFolders\Cnblogs\AlphaPanda\Main.py",line208,indivision
print'res=%s'%(a/b)
TypeError:unsupportedoperandtype(s)for/:'int'and'str'
可以看到纵使程序发生异常且没有被正确处理,在程序终止前,finally语句依旧被执行了。可以将此看做程序安全的最后一道有效屏障。主要进行一些善后清理工作,比如资源释放、断开网络连接等。当然with声明可以自动帮我们进行一些清理工作。
2.6raise抛出异常
程序执行过程中可以使用raise主动的抛出异常.
try: e=Exception('Hello','World') e.message='NiHao!' raisee exceptExceptionasinst: printtype(inst),inst,inst.args,inst.message
结果:
上面展示了except对象的属性args,message。
2.7自定义异常
绝大部分情况下内置类型的异常已经能够满足平时的开发使用,如果想要自定义异常类型,可以直接继承内置类型来实现。
classZeroDivZeroError(ZeroDivisionError): def__init__(self,value): self.value=value def__str__(self): returnrepr(self) def__repr__(self): returnself.value try: #dosomethingandfind0/0 raiseZeroDivZeroError('hahajun') exceptZeroDivZeroErroraserr: print'exceptinfo%s'%err
自定义异常应该直接继承自Exception类或其子类,而不要继承自BaseException.
3.StackTrace
python执行过程中发生异常,会告诉我们到底哪里出现问题和什么问题。这两种类型的错误信息分别为stacktrace和exception,在程序中分别用tracebackobject和异常对象表示。
Traceback(mostrecentcalllast):
File"D:\MyFolders\Cnblogs\AlphaPanda\Main.py",line270,in
1/0
ZeroDivisionError:integerdivisionormodulobyzero
上面的错误信息包含错误发生时当前的堆栈信息(stacktrace,前三行)和异常信息(exception,最后一行),分别存放在tracebackobjects和抛出的异常对象中。
异常对象及异常信息前面已经介绍过,接下来我们在看一下异常发生时,stacktrace的处理。
Tracebackobjectsrepresentastacktraceofanexception.Atracebackobjectiscreatedwhenanexceptionoccurs.
这时有两种情况:
- 异常被try...except捕获
- 没有被捕获或者干脆没有处理
正常的代码执行过程,可以使用traceback.print_stack()输出当前调用过程的堆栈信息。
3.1捕获异常
对于第一种情况可以使用下面两种方式获取stacktrace信息:
trace_str=traceback.format_exc()
或者从sys.exc_info()中获取捕获的异常对象等的信息,然后格式化成trace信息。
defget_trace_str(self): """ 从当前栈帧或者之前的栈帧中获取被except捕获的异常信息; 没有被tryexcept捕获的异常会直接传递给sys.excepthook """ t,v,tb=sys.exc_info() trace_info_list=traceback.format_exception(t,v,tb) trace_str=''.join(trace_info_list)
至于抛出的包含异常信息的异常对象则可以在try...except结构中的exceptExceptionclassase中获取。
3.2未捕获异常
第二种情况,如果异常没有被处理或者未被捕获则会在程序推出前调用sys.excepthook将traceback和异常信息输出到sys.stderr。
defexcept_hook_func(tp,val,tb): trace_info_list=traceback.format_exception(tp,val,tb) trace_str=''.join(trace_info_list) print'sys.excepthook' printtrace_str sys.excepthook=except_hook_func
上面自定义excepthook函数来取代sys.excepthook函数。在hook函数中根据异常类型tp、异常值和traceback对象tb获取stacktrace。这种情况下不能从sys.exc_info中获取异常信息。
3.3测试
defexcept_hook_func(tp,val,tb): trace_info_list=traceback.format_exception(tp,val,tb) trace_str=''.join(trace_info_list) print'sys.excepthook' printtrace_str sys.excepthook=except_hook_func try: /0 exceptTypeErrorase: res=traceback.format_exc() print"try...except" printstr(e.message) printres
走的是sys.excepthook处理流程结果:
sys.excepthook
Traceback(mostrecentcalllast):
File"D:\MyFolders\Cnblogs\AlphaPanda\Main.py",line259,in
1/0
ZeroDivisionError:integerdivisionormodulobyzero
将exceptTypeErrorase改为exceptZeroDivisionErrorase,则走的是try...except捕获异常流程,结果如下:
try...except
integerdivisionormodulobyzero
Traceback(mostrecentcalllast):
File"D:\MyFolders\Cnblogs\AlphaPanda\Main.py",line259,in
1/0
ZeroDivisionError:integerdivisionormodulobyzero
4.异常信息收集
讲了这么多,我们看一下如何实现一个程序中trace信息的收集。
classTracebackMgr(object): def_get_format_trace_str(self,t,v,tb): _trace=traceback.format_exception(t,v,tb) return''.join(_trace) defhandle_one_exception(self): """ 从当前栈帧或者之前的栈帧中获取被except捕获的异常信息; 没有被tryexcept捕获的异常会自动使用handle_traceback进行收集 """ t,v,tb=sys.exc_info() self.handle_traceback(t,v,tb,False) defhandle_traceback(self,t,v,tb,is_hook=True): """ 将此函数替换sys.excepthook以能够自动收集没有被try...except捕获的异常, 使用tryexcept处理的异常需要手动调用上面的函数handle_one_exception才能够收集 """ trace_str=self._get_format_trace_str(t,v,tb) self.record_trace(trace_str,is_hook) #dosomethingelse defrecord_trace(self,trace_str,is_hook): #Dosomethind print'is_hook:%s'%is_hook printtrace_str
其用法很简单:
trace_mgr=TracebackMgr() sys.excepthook=trace_mgr.handle_traceback try: /0 exceptExceptionase: trace_mgr.handle_one_exception() #processtrace /'0'
结果用两种方式收集到两个trace信息:
is_hook:False
Traceback(mostrecentcalllast):
File"D:\MyFolders\Cnblogs\AlphaPanda\Main.py",line299,in
/0
ZeroDivisionError:integerdivisionormodulobyzerois_hook:True
Traceback(mostrecentcalllast):
File"D:\MyFolders\Cnblogs\AlphaPanda\Main.py",line304,in
/'0'
TypeError:unsupportedoperandtype(s)for/:'int'and'str'
可以将标准的输入和输出重定向,将打印日志和错误信息输入到文件中:
classDumpfile(object): @staticmethod defwrite(str_info): withopen('./dump_file.txt','a+')asfobj: fobj.write(str_info) defflush(self): self.write('') sys.stdout=sys.stderr=Dumpfile()
trace的收集主要用到两点:如何捕获异常和两种情况下异常信息的收集,前面都介绍过。
5.总结
python异常处理:
- 使用对象来表示异常错误信息,每种异常均有一种对应的类,BaseException为所有表示异常处理类的基类。
- 程序执行过程中抛出的异常会匹配该对象对应的异常类和其所有的基类。
- 可以从内置类型的异常类派生出自定义的异常类。
- 被捕获的异常可以再次被抛出。
- 可以的话尽量使用内置的替代方案,如ifgetattr(obj,attr_name,None),或者with结构等。
- sys.exc_info()保存当前栈帧或者之前的栈帧中获取被try,except捕获的异常信息。
- 未处理的异常导致程序终止前会被sys.excpethook处理,可以自定义定义sys.excpethook。
异常的陷阱:
正确的异常处理能让代码有更好的鲁棒性,但是错误的使用异常会过犹不及。
捕获异常却忽略掉或者错误的处理是不可取的。滥用异常处理不仅达不到提高系统稳定性的效果,还会隐藏掉引起错误的诱因,导致排查问题的难度增加。
因此比如何捕获异常更重要的是,异常发生时应当如何处理。
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。