我应该如何正确处理Python3中的异常

时间:2010-05-26 14:24:20

标签: python exception logging

我无法理解我应该处理'现在和将来'会出现什么样的异常,以及我应该重新提出或者在这里不处理的异常类型,以及以后如何处理它们(在更高层次上) )。例如:我使用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怎么办?我应该在这里处理它,还是什么都不做,以后再赶上它?还有很多其他可能的例外吗?

  1. 客户端使用此类(BasicConnection)将更新的文件发送到服务器:
  2. 
    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
    

    这是对的吗?可能有人花费自己的时间,只是帮助我理解处理异常的pythonic风格?我无法理解如何处理NameError,TypeError,KeyError,ValueError等等异常.......他们可以随时抛出任何语句......以及怎样做和他们一起,如果我想记录一切。

    1. 人们通常会记录哪些信息?如果发生错误,我应该记录哪些信息呢?所有追溯,或只是关于它或其他东西的相关消息?

    2. 我希望有人帮助我。 非常感谢。

2 个答案:

答案 0 :(得分:24)

通常,您应该“捕获”您期望发生的异常(因为它们可能是由用户错误或程序控制之外的其他环境问题引起的),特别是如果您知道您的代码可能能够执行的操作做他们。只是在错误报告中提供更多细节是一个边缘问题,尽管某些程序的规范可能需要这样做(例如,由于这些问题而不应该崩溃的长期服务器,而是记录大量状态信息,给出用户摘要说明,并继续为将来的查询工作。)

NameErrorTypeErrorKeyErrorValueErrorSyntaxErrorAttributeError等可以被认为是由于程序中的错误 - 错误,而不是程序员控制之外的问题。如果您要发布一个库或框架,以便您的代码将被您控制之外的其他代码调用,那么这些错误可能很可能出现在其他代码中;你应该通常让异常传播,以帮助其他程序员调试自己的错误。如果您正在发布应用程序,那么您拥有错误,并且必须选择可帮助您找到它们的策略。

如果您的错误出现在最终用户运行程序时,您应该记录很多状态信息,并向用户提供摘要说明和道歉(如果您有可能请求向您发送日志信息无法自动化 - 或者,至少在您从用户的机器向您的机器发送任何内容之前询问权限)。到目前为止,您可以保存一些用户的工作,但通常(在一个已知有错误的程序中)可能无法正常工作。

大多数错误应该在您自己的测试过程中出现;在这种情况下,传播异常非常有用,因为您可以将其连接到调试器并探索错误的详细信息。

有时像这样的一些例外只是因为“请求宽恕比允许更容易”(EAFP) - 一种完全可以接受的Python编程技术。在那种情况下,你当然应该立即处理它们。例如:

try:
    return mylist[theindex]
except IndexError:
    return None

在这里你可能会认为theindex通常是mylist的有效索引,但偶尔会超出mylist的界限 - 而后者则是假设的语义这个片段所属的应用程序不是错误,只需要通过考虑列表在两侧使用无限数量的None来概念扩展来修复一点异常。尝试/除了正确检查索引的正值和负值(和更快,如果超出界限是非常罕见的情况)更容易。

同样适用于KeyErrorAttributeError的案例发生频率较低,这要归功于getattr内置和get dicts方法(可让您提供默认值), collections.defaultdict等;但是列表没有直接等价的列表,因此IndexError更频繁地看到try / except。

尝试捕获语法错误,输入错误,值错误,名称错误等,有点罕见且更具争议性 - 尽管如果错误是在“插件”中诊断出来肯定是合适的,第三 - 您的框架/应用程序试图加载和动态执行的控制之外的派对代码(事实上,在您提供库等的情况下,需要与您的控制中的代码和平共存,这可能是错误的)。类型和值错误有时可能发生在EAFP模式中 - 例如当你试图重载一个函数来接受一个字符串或一个数字并且在每种情况下表现得略有不同时,捕获这样的错误可能比尝试检查类型更好 - 但是这样过载的函数的概念通常比不完全可疑。

回到“用户和环境错误”,当用户给你输入时,用户将不可避免地犯错误,指出一个实际上并不存在的文件名(或者你没有阅读权限,或者如果那就是你的'应该做的事情,等等:当然应该抓住所有这些错误,并向用户清楚地解释出错的原因,以及另一个获得正确输入的机会。网络有时会出现故障,数据库或其他外部服务器可能无法按预期响应,等等 - 有时值得抓住此类问题并重试(可能需要等待一段时间后 - 可能会向用户显示出错的原因,例如他们可能不小心拔掉了电缆而你想让他们有机会解决问题并告诉你何时再试一次),有时候(特别是在无人值守的长时间运行的程序中)除了有序关机(和详细的记录)之外你什么也做不了环境的每一个可能相关的方面。)

因此,简而言之,你的Q标题的答案是“它取决于”;-)。我希望我能够列出它可以依赖的许多情况和方面,并建议对这些问题采取最有用的态度。

答案 1 :(得分:3)

首先,您不需要任何_mainLogger。 如果您想捕获任何异常,可能是通过电子邮件或其他方式记录或发送它们,请在最高级别执行此操作 - 当然不在此类中。

此外,您绝对不希望将每个Exception转换为RuntimeError。让它出现。 stopClient()方法现在没有用处。如果有,我们会看看它..

你基本上可以将ConnectionError,IOError和OSError包装在一起(比如,重新加注其他东西),但不仅仅是...