2010-05-26 15 views
12

No puedo entender qué tipo de excepciones debo manejar 'aquí y ahora', y qué tipo de excepciones debería volver a plantear o simplemente no manejar aquí, y qué hacer con ellos más tarde (en el nivel superior). Por ejemplo: escribí una aplicación cliente/servidor usando python3 con comunicación ssl. Se supone que el cliente verifica los archivos en cualquier diferencia sobre ellos, y si existe diff, entonces debe enviar este archivo 'actualizado' al servidor.¿Cómo debo manejar correctamente las excepciones en 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() 

¿Es correcto usar excepciones en python? El problema es: ¿qué ocurre si file.read() arroja IOError? ¿Debo manejarlo aquí, o simplemente no hacer nada y atraparlo más tarde? Y muchas otras posibles excepciones?

  1. cliente utilizan esta clase (BasicConnection) para enviar archivos actualizados al servidor:

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 

¿Es correcto? ¿Puede ser que alguien pase su propio tiempo y simplemente me ayude a entender el estilo pítico de manejar excepciones? No puedo entender qué hacer con excepciones como NameError, TypeError, KeyError, ValueError ... y así sucesivamente ... Se podrían lanzar en cualquier declaración, en cualquier momento ... y qué hacer con ellos, si quiero registrar todo.

  1. ¿Y qué información debe la gente generalmente registra? Si ocurre un error, ¿qué información debo registrar? ¿Todo rastreo, o solo mensaje relevante sobre él o algo más?

  2. Espero que alguien me ayude. Muchas gracias.

Respuesta

24

En general, se debe "capturar" las excepciones que se espera que suceda (ya que pueden ser causados ​​por un error del usuario, u otros problemas ambientales fuera del control de su programa), especialmente si usted sabe cuál es su código podría ser capaz de hacer sobre ellos. Solo dar más detalles en un informe de error es un problema marginal, aunque las especificaciones de algunos programas pueden requerir hacer eso (por ejemplo, un servidor de larga ejecución que no debería colapsar debido a tales problemas, sino más bien registrar una gran cantidad de información de estado, dar el usuario una explicación resumida, y solo sigue trabajando para futuras consultas).

NameError, TypeError, KeyError, ValueError, SyntaxError, AttributeError, y así sucesivamente, puede ser pensado como debido a errores en el programa - errores, no los problemas fuera del control del programador. Si está liberando una biblioteca o marco, de modo que su código va a ser llamado por otro código fuera de su control, entonces tales errores pueden estar en ese otro código; normalmente debería permitir que la excepción se propague para ayudar al otro programador a depurar sus propios errores. Si está lanzando una aplicación, es el propietario de los errores y debe elegir la estrategia que lo ayude a encontrarlos.

Si sus errores aparecen mientras un usuario final ejecuta el programa, debe registrar mucha información de estado y dar al usuario una explicación de resumen y disculpas (quizás con una solicitud para enviarle la información de registro, si no puede automatizar eso, o, al menos, pedir permiso antes de enviar algo desde la máquina del usuario a la suya). Es posible que pueda guardar parte del trabajo del usuario hasta el momento, pero a menudo (en un programa que se sabe que tiene errores) que puede no funcionar de todos modos.

La mayoría de los errores deberían aparecer durante sus propias pruebas, por supuesto; en ese caso, es útil propagar la excepción ya que puede conectarlo a un depurador y explorar los detalles del error.

A veces algunas excepciones como éstas aparecen sólo porque "es más fácil pedir perdón que permiso" (EAFP) - una técnica de programación perfectamente aceptable en Python. En ese caso, por supuesto, debes manejarlos de una vez. Por ejemplo:

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

aquí se podría esperar que theindex es generalmente un índice válido en mylist, pero en ocasiones fuera de los límites mylist 's - y el último caso, por la semántica de la aplicación hipotética en la que este fragmento pertenece, no es un error, solo una pequeña anomalía que se debe arreglar al considerar que la lista se extiende conceptualmente en ambos lados con un número infinito de None s. Es más fácil simplemente probar/exceptuar que verificar adecuadamente los valores positivos y negativos del índice (y más rápido, si estar fuera de límites es algo realmente raro).

casos Del mismo modo apropiadas para KeyError y AttributeError ocurren con menos frecuencia, gracias al método de dicts getattr orden interna y get (que permiten proporcionar un valor por defecto), collections.defaultdict, etc; pero las listas no tienen un equivalente directo de esas, por lo que try/except se ve con más frecuencia para IndexError.

Intentar detectar errores de sintaxis, escribir errores, valorar errores, nombrar errores, etc., es un poco más raro y más controvertido, aunque seguramente sería apropiado si el error se diagnosticó en un "complemento", tercero código -party fuera de su control que su marco/aplicación está intentando cargar y ejecutar de forma dinámica (de hecho ese es el caso en el que está suministrando una biblioteca o similar y que coexistir pacíficamente con código fuera de su control, que bien podría ser con errores) . Los errores de tipo y valor a veces pueden ocurrir dentro de un patrón de EAFP, p. cuando intenta sobrecargar una función para aceptar una cadena o un número y se comportan de forma ligeramente diferente en cada caso, la captura de este tipo de errores puede ser mejor que tratar de comprobar los tipos - pero el mismo concepto de funciones tanto sobrecargados, es más a menudo que no del todo dudoso.

Volviendo a "errores de usuario y medioambientales", los usuarios inevitablemente cometerán errores cuando le den entrada, indiquen un nombre de archivo que no está realmente cerca (o que no tiene permiso para leer, o escribir si eso es lo que se supone que debe estar haciendo), y así sucesivamente: todos estos errores deberían ser capturados y dar como resultado una explicación clara para el usuario sobre lo que salió mal, y otra oportunidad para obtener la información correcta. Las redes en algún momento disminuyen, las bases de datos u otros servidores externos pueden no responder como se esperaba, y así sucesivamente - a veces vale la pena detectar dichos problemas y volver a intentar (quizás después de esperar un poco) tal vez con una indicación al usuario sobre lo que está mal, por ejemplo puede haber desenchufado accidentalmente un cable y desea darles la oportunidad de arreglarlo y decirle cuándo volver a intentarlo), a veces (especialmente en programas desatendidos de larga ejecución) no hay mucho que pueda hacer excepto un apagado ordenado (y registro detallado de cada aspecto posiblemente relevante del entorno).

Así que, en resumen, la respuesta al título de su Q es: "Depende" ;-). Espero haber sido útil para enumerar muchas de las situaciones y aspectos de los que puede depender, y recomendar cuál es, en general, la actitud más útil que se debe tomar con respecto a estos temas.

+1

introspección útil de algunos "sentimientos viscerales" que vienen con el experiencia con python. Espero ansiosamente tus mejores prácticas de Python, pero no antes de Python Patterns :) –

3

Para empezar, usted no necesita ningún _mainLogger. Si se desea capturar cualquier excepción, tal vez para iniciar la sesión o enviarlos por correo electrónico o lo que sea, hacer que en el nivel más alto posible - sin duda no dentro de esta clase.

Además, definitivamente no desea convertir todas las excepciones a RuntimeError. Déjalo emerger. El método stopClient() no tiene ningún propósito en este momento. Cuando lo tenga, lo veremos ...

Se podría básicamente envolver el ConnectionError, IOError y OSError juntos (como, volver a subir como otra cosa), pero no mucho más que eso ...

Cuestiones relacionadas