2010-05-26 42 views
12

我无法理解我应该处理什么样的异常,以及什么样的异常我应该重新提出或者不在这里处理,以及什么稍后(在更高层)与他们做。例如:我用ssl通信编写了使用python3的客户机/服务器应用程序。客户端应该验证文件上是否存在差异,如果存在差异,则应将此“更新”文件发送到服务器。我应该如何正确处理Python3中的异常


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)将更新的文件发送到服务器:

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. 我希望有人能帮助我。 非常感谢。

回答

24

在一般情况下,你应该“抓住”,你希望发生(因为它们可能会由于用户的错误,或者你的程序的控制范围之外的其他环境问题引起的),特别是如果你知道例外你的代码可能会对他们做些事情。只是在错误报告中提供更多细节是一个边缘问题,尽管某些程序的规格可能需要这样做(例如,长期运行的服务器不应该由于此类问题而崩溃,而是记录大量状态信息,用户提供一个总结说明,并继续为未来的查询工作)。

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

如果在最终用户运行程序时出现错误,则应记录大量状态信息,并向用户提供摘要说明和道歉信息(可能会向您发送日志信息请求,如果你不能自动化 - 或者至少在你从用户的机器发送任何东西之前请求许可)。您目前可能能够保存一些用户的工作,但通常(在一个已知有问题的程序中)可能无法正常工作。

大多数错误应该显示在你自己的测试当然;在这种情况下,传播异常很有用,因为您可以将它连接到调试器并查看错误的详细信息。

有时候会出现一些类似的例外,仅仅是因为“请求原谅比权限更容易”(EAFP) - 这是一种完全可以接受的Python编程技术。在那种情况下,你应该立即处理它们。例如:

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

在这里,你可能会认为theindex通常是有效的索引mylist,但偶尔外mylist的界限的 - 而后者情况下,通过假设的应用程序的语义在这个片段中属于,不是一个错误,只是通过考虑列表在概念上在两侧用无限数量的None s扩展来解决一点小小的异常。只是尝试/比正确检查索引的正值和负值更容易(并且更快,如果出界是真正罕见的情况)。

同样适当情况下为KeyErrorAttributeError发生频率较低,这要归功于http://stardict.sourceforge.net/Dictionaries.php下载的getattr内置和get方法(这让你提供一个默认值),collections.defaultdict等;但是列表并没有直接相当于那些,所以对于IndexError来说,尝试/除外更频繁。

试图捕捉语法错误,输入错误,值错误,名称错误等,这是比较罕见和更有争议的 - 尽管如果错误在“插件”中被诊断,它肯定是合适的,第三你的框架/应用程序试图加载和动态执行的部分代码(如果你正在提供一个库或类似的东西,并且需要和你的控制器中的代码和平共存,这可能是错误的) 。类型和值错误有时可能发生在EAFP模式中 - 例如当你试图重载一个函数来接受一个字符串或一个数字并且在每种情况下的行为都略有不同时,捕获这样的错误可能比试图检查类型更好 - 但是这样重载的函数的概念往往不是很完美可疑。

回到“用户和环境错误”,用户在给你输入时会不可避免地犯错误,指出一个文件名实际上并没有(或者你没有权限阅读,或者如果这就是你所写的应该是这样做的)等等:所有这些错误当然应该被捕获并且导致向用户清楚地解释出了什么问题,并且得到正确输入的另一个机会。网络有时会停机,数据库或其他外部服务器可能无法按预期做出响应,等等 - 有时值得捕捉这些问题并重试(可能稍后等待 - 可能会向用户指示有什么问题,例如它们可能不小心拔下了电缆,并且您想让他们有机会解决问题并告诉您何时再试一次),有时(特别是在无人值守的长时间运行的程序中),除了有序关闭(和详细日志记录环境的每个可能相关方面)。

因此,简而言之,你Q的标题的答案是“,这取决于”;-)。我希望在列举它可以依赖的许多情况和方面时,我已经习惯于使用它,并且建议对这些问题采取一般最有用的态度。

+1

有用的一些“直觉”来与自省使用Python的经验。我急切地等待您的Python最佳实践 - 但不是Python模式之前:) – 2010-05-26 21:33:48

3

首先,您不需要任何_mainLogger。 如果你想捕捉任何异常情况,也许要通过电子邮件或其他方式记录或发送它们,请尽可能在最高级别上执行 - 当然不是在本课程中。

此外,您绝对不希望将每个异常转换为RuntimeError。让它出现。 stopClient()方法现在没有任何用途。当它有,我们会看看它..

你可以基本上包裹ConnectionError,IO错误和OSERROR在一起(如,重新筹集别的东西),但没有比这更多...