我应该如何正确处理Python3中的异常
我不太明白在这里应该处理什么样的异常,什么样的异常应该重新抛出,或者干脆不处理,之后再在更高层次上处理。举个例子:我用Python3写了一个客户端/服务器应用,使用了SSL通信。客户端需要检查文件之间的差异,如果有差异,就应该把这个“更新过的”文件发送给服务器。
class BasicConnection:
#blablabla
def sendMessage(self, sock, url, port, fileToSend, buffSize):
try:
sock.connect((url, port))
while True:
data = fileToSend.read(buffSize)
if not data: break
sock.send(data)
return True
except socket.timeout as toErr:
raise ConnectionError("TimeOutError trying to send File to remote socket: %s:%d"
% (url,port)) from toErr
except socket.error as sErr:
raise ConnectionError("Error trying to send File to remote socket: %s:%d"
% (url,port)) from sErr
except ssl.SSLError as sslErr:
raise ConnectionError("SSLError trying to send File to remote socket: %s:%d"
% (url,port)) from sslErr
finally:
sock.close()
在Python中使用异常的方式对吗?问题是:如果file.read()抛出IOError,我应该在这里处理它,还是干脆不管,等到后面再处理?还有很多其他可能的异常呢?
- 客户端使用这个类(BasicConnection)来发送更新的文件到服务器:
class PClient():
def __init__(self, DATA):
'''DATA = { 'sendTo' : {'host':'','port':''},
'use_ssl' : {'use_ssl':'', 'fileKey':'', 'fileCert':'', 'fileCaCert':''},
'dirToCheck' : '',
'localStorage': '',
'timeToCheck' : '',
'buffSize' : '',
'logFile' : ''} '''
self._DATA = DATA
self._running = False
self.configureLogging()
def configureLogging(self):
#blablabla
def isRun(self):
return self._running
def initPClient(self):
try:
#blablabla
return True
except ConnectionError as conErr:
self._mainLogger.exception(conErr)
return False
except FileCheckingError as fcErr:
self._mainLogger.exception(fcErr)
return False
except IOError as ioErr:
self._mainLogger.exception(ioErr)
return False
except OSError as osErr:
self._mainLogger.exception(osErr)
return False
def startPClient(self):
try:
self._running = True
while self.isRun():
try :
self._mainLogger.debug("Checking differences")
diffFiles = FileChecker().checkDictionary(self._dict)
if len(diffFiles) != 0:
for fileName in diffFiles:
try:
self._mainLogger.info("Sending updated file: %s to remote socket: %s:%d"
% (fileName,self._DATA['sendTo']['host'],self._DATA['sendTo']['port']))
fileToSend = io.open(fileName, "rb")
result = False
result = BasicConnection().sendMessage(self._sock, self._DATA['sendTo']['host'],
self._DATA['sendTo']['port'], fileToSend, self._DATA['buffSize'])
if result:
self._mainLogger.info("Updated file: %s was successfully delivered to remote socket: %s:%d"
% (fileName,self._DATA['sendTo']['host'],self._DATA['sendTo']['port']))
except ConnectionError as conErr:
self._mainLogger.exception(conErr)
except IOError as ioErr:
self._mainLogger.exception(ioErr)
except OSError as osErr:
self._mainLogger.exception(osErr)
self._mainLogger.debug("Updating localStorage %s from %s " %(self._DATA['localStorage'], self._DATA['dirToCheck']))
FileChecker().updateLocalStorage(self._DATA['dirToCheck'],
self._DATA['localStorage'])
self._mainLogger.info("Directory %s were checked" %(self._DATA['dirToCheck']))
time.sleep(self._DATA['timeToCheck'])
except FileCheckingError as fcErr:
self._mainLogger.exception(fcErr)
except IOError as ioErr:
self._mainLogger.exception(ioErr)
except OSError as osErr:
self._mainLogger.exception(osErr)
except KeyboardInterrupt:
self._mainLogger.info("Shutting down...")
self.stopPClient()
except Exception as exc:
self._mainLogger.exception(exc)
self.stopPClient()
raise RuntimeError("Something goes wrong...") from exc
def stopPClient(self):
self._running = False
这样做对吗?有没有人愿意花时间帮我理解一下在Python中处理异常的正确方式?我搞不清楚像NameError、TypeError、KeyError、ValueError这些异常该怎么处理……它们可能在任何语句、任何时候被抛出……如果我想记录所有的异常,该怎么做呢?
通常人们应该记录哪些信息?如果发生错误,我应该记录哪些信息?是记录所有的追踪信息,还是只记录相关的错误信息,或者其他什么呢?
希望有人能帮我。非常感谢。
2 个回答
首先,你不需要什么 _mainLogger。
如果你想捕捉到任何异常,比如记录日志或者发邮件之类的,最好在最上层处理这些异常——绝对不要在这个类里面处理。
另外,你也不应该把每个异常都转成 RuntimeError。让它自然出现。现在 stopClient() 方法没有什么用,等它有用的时候我们再来看看。
你可以把 ConnectionError、IOError 和 OSError 这几种异常一起处理(比如,把它们重新抛出成其他类型),但也就这么多了……
一般来说,你应该“捕捉”那些你预期会发生的错误(因为这些错误可能是用户操作不当,或者是程序无法控制的环境问题造成的),尤其是当你知道你的代码可以如何处理这些错误时。虽然在错误报告中提供更多细节是次要问题,但有些程序的规范可能要求这样做(例如,一个不应该因为这些问题而崩溃的长时间运行的服务器,而是应该记录大量状态信息,给用户一个总结性的解释,并继续处理后续请求)。
NameError
、TypeError
、KeyError
、ValueError
、SyntaxError
、AttributeError
等等,可以看作是程序内部的错误——也就是bug,而不是程序员无法控制的问题。如果你发布的是一个库或框架,让其他代码调用你的代码,那么这些bug很可能出现在其他代码中;通常情况下,你应该让异常继续传播,以帮助其他程序员调试他们自己的bug。如果你发布的是一个应用程序,那么这些bug就是你的责任,你必须选择一种策略来帮助你找到它们。
如果你的bug在最终用户运行程序时出现,你应该记录大量的状态信息,并给用户一个总结性的解释和道歉(如果你无法自动化这个过程,或至少在发送任何信息之前征得用户的同意)。你可能能够保存用户到目前为止的一些工作,但在一个已知存在bug的程序中,这通常也可能无法成功。
当然,大多数bug应该在你自己的测试中出现;在这种情况下,传播异常是有用的,因为你可以将其连接到调试器,深入探讨bug的细节。
有时候,一些异常的出现只是因为“请求原谅比请求许可更容易”(EAFP)——这是Python中一个完全可以接受的编程技巧。在这种情况下,你当然应该立即处理它们。例如:
try:
return mylist[theindex]
except IndexError:
return None
在这里,你可能期望theindex
通常是mylist
的有效索引,但偶尔会超出mylist
的范围——而在这个假设的应用程序中,这种情况并不是错误,只是一个小的异常,可以通过将列表概念上扩展到两边无限数量的None
来解决。尝试/异常处理比正确检查索引的正负值要简单得多(如果超出范围的情况确实很少发生,速度也更快)。
类似的,KeyError
和AttributeError
的适用情况较少,得益于getattr
内置函数和字典的get
方法(允许你提供默认值),collections.defaultdict
等;但列表没有这些的直接等价物,因此对于IndexError
,尝试/异常处理的情况更为常见。
尝试捕捉语法错误、类型错误、值错误、名称错误等情况则相对少见且更具争议——不过如果错误是在“插件”或第三方代码中诊断出来的,而你的框架/应用程序试图动态加载和执行这些代码,那么这样做当然是合适的(实际上这是你提供库或类似内容时,需要与可能存在bug的外部代码和平共处的情况)。类型和值错误有时可能在EAFP模式中出现——例如,当你尝试重载一个函数以接受字符串或数字,并在每种情况下表现略有不同,捕捉这些错误可能比检查类型更好——但这种重载函数的概念往往是相当可疑的。
回到“用户和环境错误”,用户在输入时不可避免地会犯错误,比如指定一个实际上不存在的文件名(或者你没有权限读取或写入的文件名),等等:所有这些错误当然应该被捕捉,并给用户一个清晰的解释,说明出了什么问题,并给予他们重新输入的机会。网络有时会中断,数据库或其他外部服务器可能无法按预期响应,等等——有时捕捉这些问题并重试是值得的(也许在稍等片刻后——也许给用户一个关于出什么问题的提示,例如,他们可能不小心拔掉了电缆,你想给他们一个机会来修复问题,并告诉你何时重试),有时(尤其是在无人值守的长时间运行程序中)你能做的也只是有序关闭程序(并详细记录环境中每个可能相关的方面)。
所以,总的来说,你的问题标题的答案是,“这要看情况”;-)。我希望我列出了许多可能的情况和方面,并推荐了对这些问题通常最有用的态度。