关于python:我应该如何正确处理Python3中的异常

How should I correctly handle exceptions in Python3

我不明白我应该在这里和现在处理什么类型的异常,我应该重新提出什么类型的异常或者不在这里处理,以及以后(在更高层)如何处理它们。例如:我使用python3和ssl通信编写了客户机/服务器应用程序。客户机应该验证文件之间的差异,如果存在差异,那么它应该将这个"更新的"文件发送到服务器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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)将更新的文件发送到服务器:
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    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…等等异常。它们可以在任何时候被抛出到任何语句中…如果我想记录下所有的信息,那么如何处理它们呢?

  • 人们通常应该记录哪些信息?如果发生错误,我应该记录关于它的什么信息?所有的追溯,或只是有关它的相关信息或其他什么?

  • 我希望有人能帮助我。谢谢。


  • 一般来说,您应该"捕获"期望发生的异常(因为它们可能是由用户错误或程序控制之外的其他环境问题引起的),特别是当您知道您的代码可以对它们做什么时。仅仅在错误报告中提供更多的细节是一个边缘问题,尽管一些程序的规格可能需要这样做(例如,一个长期运行的服务器,它不应该因为这样的问题而崩溃,而是记录大量的状态信息,给用户一个概要的解释,并继续为将来的查询工作)。好的。

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

    如果在最终用户运行程序时出现错误,您应该记录大量状态信息,并向用户简要解释和道歉(可能是请求向您发送日志信息,如果您不能自动执行,或者至少在将任何内容从用户的计算机发送到您的计算机之前请求权限)。到目前为止,您可能可以保存一些用户的工作,但通常(在一个已知有问题的程序中)这些工作可能无论如何都不起作用。好的。

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

    有时,像这样的异常出现仅仅是因为"请求原谅比请求许可更容易"(EAFP),这是Python中一种完全可以接受的编程技术。在这种情况下,你当然应该马上处理它们。例如:好的。

    1
    2
    3
    4
    try:
        return mylist[theindex]
    except IndexError:
        return None

    在这里,您可能期望theindex通常是进入mylist的有效索引,但偶尔在mylist的界限之外——后者,根据这个片段所属的假设应用程序的语义,不是一个错误,只是考虑到列表在概念上扩展到两边,可以修复一点异常。有无穷多的Nones.只需尝试/排除,比正确检查索引的正负值更容易(如果超出界限是一种真正罕见的情况,则速度更快)。好的。

    同样适用于KeyErrorAttributeError的情况发生的频率也较低,这是由于getattr内置和get的dicts方法(允许您提供默认值)、collections.defaultdict等;但列表没有直接等效的情况,所以对IndexError的尝试/例外情况更为常见。好的。

    尝试捕捉语法错误、类型错误、值错误、名称错误等,是比较少见和有争议的——尽管如果错误是在"插件"中诊断出来的,那么在框架/应用程序试图动态加载和执行的控件之外的第三方代码中诊断出来肯定是合适的(事实上,在这种情况下,升级一个库或类似的库,需要与您无法控制的代码和平共存,这很可能是错误的)。类型和值错误有时可能发生在EAFP模式中——例如,当您试图重载一个函数以接受字符串或数字,并且在每种情况下的行为略有不同时,捕获此类错误可能比尝试检查类型要好——但是这样重载的函数的概念往往是不太可疑的。好的。

    回到"用户和环境错误",用户在给您输入时不可避免地会出错,指出一个实际上不存在的文件名(或者您没有读或写的权限,如果这是您应该做的),等等:当然,所有这些错误都应该被捕获,并对使用做出明确的解释。关于哪里出了问题,以及另一个获得正确输入的机会。网络有时会出现故障,数据库或其他外部服务器可能不会像预期的那样响应,等等--有时值得注意这些问题并重试(可能在稍等之后--可能会向用户指示出问题所在,例如,他们可能意外拔出电缆,而您想让他们有机会修复问题然后告诉你什么时候再试),有时候(特别是在无人值守的长时间运行的程序中),除了有序的关闭(以及环境中每个可能相关方面的详细日志记录)之外,你什么都做不了。好的。

    所以,简而言之,你的问题的答案是,"它取决于";-)。我希望我已经在列出它可以依赖的许多情况和方面,并推荐对这些问题采取最有用的态度。好的。好啊。


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

    另外,您肯定不想将每个异常都转换为runtimeerror。让它出现。stopclient()方法目前没有任何用途。一旦有了,我们就看看……

    您基本上可以将connectionerror、ioerror和oserror包装在一起(比如,像其他东西一样重新提升),但不会超过这个…