2009-06-12 15 views
26

Ich habe nach anderen Posts gesucht, da ich dachte, dass dies ein ziemlich häufiges Problem ist, aber alle anderen Python-Ausnahmefragen, die ich gefunden habe, spiegelten mein Problem nicht wider.Korrekter Weg, Ausnahmen in Python zu behandeln?

Ich werde versuchen, so spezifisch wie möglich zu sein, also werde ich ein direktes Beispiel geben. Und peeeeease postet keine Problemumgehungen für dieses spezielle Problem. Ich bin nicht speziell daran interessiert, wie man mit xyz eine Email viel schöner versenden kann. Ich möchte wissen, wie Sie im Allgemeinen mit abhängigen, fehleranfälligen Aussagen umgehen.

Meine Frage ist, wie man Ausnahmen gut behandelt, die voneinander abhängen, was bedeutet: Nur wenn der erste Schritt erfolgreich war, versuchen Sie die nächste, und so weiter. Ein weiteres Kriterium ist: Alle Ausnahmen müssen abgefangen werden, dieser Code muss robust sein.

Für Ihre Überlegung, ein Beispiel:

try: 
    server = smtplib.SMTP(host) #can throw an exception 
except smtplib.socket.gaierror: 
    #actually it can throw a lot more, this is just an example 
    pass 
else: #only if no exception was thrown we may continue 
    try: 
     server.login(username, password) 
    except SMTPAuthenticationError: 
     pass # do some stuff here 
    finally: 
     #we can only run this when the first try...except was successful 
     #else this throws an exception itself! 
     server.quit() 
    else: 
     try: 
      # this is already the 3rd nested try...except 
      # for such a simple procedure! horrible 
      server.sendmail(addr, [to], msg.as_string()) 
      return True 
     except Exception: 
      return False 
     finally: 
      server.quit() 

return False 

Das sieht extrem unpythonic mir, und der Fehlercode Handhabung ist das Dreifache der realen Business-Code, aber auf der anderen Seite, wie kann ich einige Aussagen behandeln, sind voneinander abhängig, dh Aussage1 ist Voraussetzung für Aussage2 und so weiter?

Ich interessiere mich auch für die ordnungsgemäße Ressourcenbereinigung, sogar Python kann das für sich selbst verwalten.

Danke, Tom

+1

danke für die Bearbeitung dbr, aber bitte nicht bearbeiten Dinge, die Sie nicht selbst sicher sind. Ich bearbeitet zurück Kriterien zu Kriterium, das ist in der Tat die Einzahl, wie ein Plural keinen Sinn macht, wo Sie es bearbeitet. – Tom

+0

Opps, tut mir leid (hm, glaube nicht, dass ich jemals die Singular der Kriterien gehört habe ..) – dbr

Antwort

24

Anstatt den Versuch zu verwenden/außer else-Block ist, können Sie einfach zurück, wenn es Fehler:

def send_message(addr, to, msg): 
    ## Connect to host 
    try: 
     server = smtplib.SMTP(host) #can throw an exception 
    except smtplib.socket.gaierror: 
     return False 

    ## Login 
    try: 
     server.login(username, password) 
    except SMTPAuthenticationError: 
     server.quit() 
     return False 

    ## Send message 
    try: 
     server.sendmail(addr, [to], msg.as_string()) 
     return True 
    except Exception: # try to avoid catching Exception unless you have too 
     return False 
    finally: 
     server.quit() 

, die perfekt lesbar und Pythonic ist ..

Ein anderer Weg, dies zu tun ist, anstatt Sorge über die konkrete Umsetzung entscheiden, wie Sie Ihren Code zum Beispiel zu sehen, wollen ..

sender = MyMailer("username", "password") # the except SocketError/AuthError could go here 
try: 
    sender.message("addr..", ["to.."], "message...") 
except SocketError: 
    print "Couldn't connect to server" 
except AuthError: 
    print "Invalid username and/or password!" 
else: 
    print "Message sent!" 

Dann den Code für die message() Methode, ca schreiben tching alle Fehler, die Sie erwarten, und erhöhen Sie Ihre eigenen benutzerdefinierten, und behandeln Sie, wo es relevant ist. Ihre Klasse kann so etwas wie aussehen ..

class ConnectionError(Exception): pass 
class AuthError(Exception): pass 
class SendError(Exception): pass 

class MyMailer: 
    def __init__(self, host, username, password): 
     self.host = host 
     self.username = username 
     self.password = password 

    def connect(self): 
     try: 
      self.server = smtp.SMTP(self.host) 
     except smtplib.socket.gaierror: 
      raise ConnectionError("Error connecting to %s" % (self.host)) 

    def auth(self): 
     try: 
      self.server.login(self.username, self.password) 
     except SMTPAuthenticationError: 
      raise AuthError("Invalid username (%s) and/or password" % (self.username)) 

    def message(self, addr, to, msg): 
     try: 
      server.sendmail(addr, [to], msg.as_string()) 
     except smtplib.something.senderror, errormsg: 
      raise SendError("Couldn't send message: %s" % (errormsg)) 
     except smtp.socket.timeout: 
      raise ConnectionError("Socket error while sending message") 
+4

+1 Ich mag wirklich, wie Sie die "Bibliothek nur eine Ausnahme für alles verwendet" gelöst haben Problem –

+1

In Ihrem ersten Beispiel wird send_message() immer nach dem Server.login() zurückgeben und nie die Nachricht senden. Ich denke nicht, dass es für diese Aussage ein endgültiges Ergebnis geben sollte. – mhawke

+1

dann läuft es jetzt auf eine Grundsatzfrage hinaus.Ihr erster Code ist im Grunde der gleiche wie meiner, außer dass Sie die Ausnahmen nicht verschachteln, wie ich es in einem "else" -Baum getan habe, was in den Python-Dokumenten vorgeschlagen wurde. Welches ist eine bessere Praxis? Die Docs geben an, dass sonst immer lieber als zusätzliche Anweisungen im try-Block bevorzugt werden sollten. es ist im Grunde die gleiche Frage mit ifs: Ist es besser, vor einem anderen zurückzukehren, wenn, oder ist es besser, die Wenns bedingt zu verschachteln. – Tom

0

Warum nicht einen großen Versuch: Block? Auf diese Weise werden Sie, wenn eine Ausnahme abgefangen wird, den ganzen Weg zur Ausnahme gehen. Und solange alle Ausnahmen für die verschiedenen Schritte unterschiedlich sind, können Sie immer feststellen, welcher Teil die Ausnahme ausgelöst hat.

+0

weil ein großer Versuch Block dir nicht die Chance gibt, sich um Ressourcen zu kümmern, zB: statement1 hat gearbeitet, und a Datei, aber Anweisung 2 löst eine Ausnahme aus Sie können das nicht in einem einzigen finally-Block handhaben, weil Sie nie wissen, ob die Ressource tatsächlich zugewiesen wurde oder nicht, außerdem können einige Schritte die gleichen Exceptions auslösen später, und Sie können Fehler nicht drucken, weil Sie nicht sicher sind, welche Anweisung tatsächlich fehlgeschlagen ist. – Tom

+0

Verschachtelt mit Blöcken, dann? http://docs.python.org/whatsnew/2.5.html#pe p-343 –

+0

Leider kann ich mich nicht darauf verlassen, dass __enter__ und __exit__ Methoden für alle Operationen definiert sind, die ich verwenden werde, also möglicherweise immer nicht funktioniert – Tom

12

Im Allgemeinen möchten Sie so wenig Testblöcke wie möglich verwenden, um Fehlerbedingungen durch die Art der Ausnahmen zu unterscheiden, die sie auslösen. Zum Beispiel, hier ist meine Refactoring des Codes Sie auf dem Laufenden:

try: 
    server = smtplib.SMTP(host) 
    server.login(username, password) # Only runs if the previous line didn't throw 
    server.sendmail(addr, [to], msg.as_string()) 
    return True 
except smtplib.socket.gaierror: 
    pass # Couldn't contact the host 
except SMTPAuthenticationError: 
    pass # Login failed 
except SomeSendMailError: 
    pass # Couldn't send mail 
finally: 
    if server: 
     server.quit() 
return False 

Hier nutzen wir die Tatsache, dass smtplib.SMTP(), server.login() und server.sendmail() alle verschiedenen Ausnahmen werfen abzuflachen der Baum der Try-Catch-Blöcke. Im finally-Block testen wir Server explizit, um den Aufruf von quit() für das nil-Objekt zu vermeiden.

Wir haben auch drei sequentielle Try-Catch-Blöcke verwenden könnte, falsch in den Ausnahmebedingungen zurückkehrt, wenn es überlappende Ausnahmefälle sind, die separat behandelt werden müssen:

try: 
    server = smtplib.SMTP(host) 
except smtplib.socket.gaierror: 
    return False # Couldn't contact the host 

try: 
    server.login(username, password) 
except SMTPAuthenticationError: 
    server.quit() 
    return False # Login failed 

try: 
    server.sendmail(addr, [to], msg.as_string()) 
except SomeSendMailError: 
    server.quit() 
    return False # Couldn't send mail 

return True 

Dies ist nicht ganz so schön, da Sie den Server an mehr als einem Ort töten müssen, aber jetzt können wir bestimmte Ausnahmetypen auf unterschiedliche Weise an verschiedenen Orten behandeln, ohne einen zusätzlichen Status zu erhalten.

+5

der Punkt ist, wie oben gesagt, dass sie nicht einzelne Ausnahmen werfen, so kann es nicht trivial abgeflacht werden. Falls Ihre Verbindung unterbrochen wird, bevor Sie sich authentifizieren können, können server.login und server.sendMail die gleiche Ausnahme auslösen ("connect to server first"). Aber wie ich oben bereits sagte, suche ich nicht nach einer Lösung für dieses spezielle Problem. Ich interessiere mich mehr für einen allgemeinen Ansatz, wie ich das lösen kann. Ihr zweiter Ansatz ist im Grunde mein Code ohne "sonst". es ist hübscher, obwohl ich zugeben muss;) – Tom

+1

Achten Sie auf den finally-Block - Sie sollten den Server vor dem Block auf None setzen, um nicht versehentlich auf eine nicht existierende Variable zu verweisen. –

+0

@Tom +1, deshalb habe ich diese Lösung nicht vorgeschlagen. – Unknown

0

Ich mag Davids Antwort, aber wenn Sie auf den Server-Ausnahmen stecken, können Sie auch nach Server suchen, wenn keine oder Staaten. Ich habe die Methode ein wenig abgeflacht, sie ist immer noch ein unpythonisches, aber in der unteren Logik besser lesbar.

server = None 

def server_obtained(host): 
    try: 
     server = smtplib.SMTP(host) #can throw an exception 
     return True 
    except smtplib.socket.gaierror: 
     #actually it can throw a lot more, this is just an example 
     return False 

def server_login(username, password): 
    loggedin = False 
    try: 
     server.login(username, password) 
     loggedin = True 
    except SMTPAuthenticationError: 
     pass # do some stuff here 
    finally: 
     #we can only run this when the first try...except was successful 
     #else this throws an exception itself! 
     if(server is not None): 
      server.quit() 
    return loggedin 

def send_mail(addr, to, msg): 
    sent = False 
    try: 
     server.sendmail(addr, to, msg) 
     sent = True 
    except Exception: 
     return False 
    finally: 
     server.quit() 
    return sent 

def do_msg_send(): 
    if(server_obtained(host)): 
     if(server_login(username, password)): 
      if(send_mail(addr, [to], msg.as_string())): 
       return True 
    return False 
+0

in beiden server_login und send_mail können Sie die lokale Variable vermeiden, weil schließlich immer ausgeführt wird, auch wenn Sie eine "return" in einem try oder except Block verwenden :) Sie können einfach im try-Block True und False in der Ausnahme zurückgeben blockieren, anstatt den Status in einer lokalen Variablen zu speichern. – Tom

1

Mit nur einem Try-Block ist der Weg zu gehen. Dies ist genau das, was sie sind entworfen für: nur die nächste Anweisung ausführen, wenn die vorherige Anweisung keine Ausnahme ausgelöst. Wie für die Ressourcenbereinigungen, vielleicht können Sie die Ressource überprüfen, wenn es gereinigt werden muss (z. B. myfile.is_open(), ...) Dies fügt einige zusätzliche Bedingungen hinzu, aber sie werden nur im Ausnahmefall ausgeführt. Zur Behandlung des Falls , dass die gleiche Ausnahme aus verschiedenen Gründen ausgelöst werden kann, sollten Sie den Grund von der Ausnahme abrufen können.

Ich schlage vor, Code wie folgt:

server = None 
try: 
    server = smtplib.SMTP(host) #can throw an exception 
    server.login(username, password) 
    server.sendmail(addr, [to], msg.as_string()) 
    server.quit() 
    return True 
except smtplib.socket.gaierror: 
    pass # do some stuff here 
except SMTPAuthenticationError: 
    pass # do some stuff here 
except Exception, msg: 
    # Exception can have several reasons 
    if msg=='xxx': 
     pass # do some stuff here 
    elif: 
     pass # do some other stuff here 

if server: 
    server.quit() 

return False 

Es ist nicht ungewöhnlich, dass Fehlercode überschreitet Code Geschäfts Handhabung. Die korrekte Fehlerbehandlung kann komplex sein. Um die Wartbarkeit zu erhöhen, hilft es jedoch, den Geschäftscode vom Fehlerbehandlungscode zu trennen.

+0

ich stimme nicht zu, weil, wie ich schon einige Male oben erwähnt habe, die Fehlermeldungen zweideutig sein würden, da 2 verschiedene Funktionsaufrufe, zum Beispiel login und sendmail, dieselbe Ausnahme auslösen könnten. Wenn Sie an den Benutzer oder an Ihr Protokoll drucken möchten: "login() wegen xyz fehlgeschlagen" oder "sendmail() wegen xyz fehlgeschlagen", ist dies nicht möglich, da beide Aufrufe zur gleichen Ausnahme führen können. Ich möchte ausführlich behandelt werden, was beim Logging falsch lief. – Tom

+0

Die Ausnahme sollte in der Lage sein, ihre Details zur Verfügung zu stellen. Z.B. Sie können "except gaierror, (code, message):" anstelle von simple "außer gaierror:" verwenden. Dann haben Sie den Fehlercode und die Fehlermeldung und können sie für eine detaillierte Fehlerbehandlung verwenden, z. if code == 11001: print "unbekannter Hostname:", Nachricht – Ralph

+0

Ich glaube, Sie haben nicht ganz verstanden, was ich sagen wollte. Versuchen Sie Folgendes: Machen Sie sich ein SMTP-Objekt, und versuchen Sie dann smtp.login() ohne Verbindung und dann smtp.sendmail() ohne Verbindung, werden Sie sehen, dass sie 100% identische Ausnahmen werfen, die Sie nicht unterscheiden können, weder durch msg noch von errno – Tom

3

Wenn es mir war ich wohl so etwas wie das folgende tun würde:

try: 
    server = smtplib.SMTP(host) 
    try: 
     server.login(username, password) 
     server.sendmail(addr, [to], str(msg)) 
    finally: 
     server.quit() 
except: 
    debug("sendmail", traceback.format_exc().splitlines()[-1]) 
    return True 

Alle Fehler werden gefangen und auf Fehler, den Rückgabewert == true bei Erfolg und die Serververbindung wird ordnungsgemäß bereinigt, wenn die anfängliche Verbindung hergestellt wird.

+0

das sieht für mich intuitiv aus, und das ist wahrscheinlich auch, wie es in Java aussehen würde, weil du dort nicht den Luxus einer "else" Aussage hast. Sie hatten das auch nicht vor python2.5 iirc. Es wurde in der Dokumentation eingeführt, um große try-Blöcke zu vermeiden, aber trotzdem die Gruppierung schön und offen zu halten, also wird der gesamte Code, der zusammen gehört, immer noch im selben Versuch sein ... außer ... else block, ohne den try-Teil auszublenden Eigenschaften. Da die Pythonianer so sehr an ihren Style Guides und Peps hängen, dachte ich, das wäre der richtige Weg. – Tom

1

würde ich so etwas wie dies versuchen:

class Mailer(): 

    def send_message(self): 
     exception = None 
     for method in [self.connect, 
         self.authenticate, 
         self.send, 
         self.quit]: 
      try: 
       if not method(): break 
      except Exception, ex: 
       exception = ex 
       break 

     if method == quit and exception == None: 
      return True 

     if exception: 
      self.handle_exception(method, exception) 
     else: 
      self.handle_failure(method) 

    def connect(self): 
     return True 

    def authenticate(self): 
     return True 

    def send(self): 
     return True 

    def quit(self): 
     return True 

    def handle_exception(self, method, exception): 
     print "{name} ({msg}) in {method}.".format(
      name=exception.__class__.__name__, 
      msg=exception, 
      method=method.__name__) 

    def handle_failure(self, method): 
     print "Failure in {0}.".format(method.__name__) 

Alle Methoden (einschließlich send_message, wirklich) das gleiche Protokoll wie folgt vor: sie zurückkehren wahr, wenn sie erfolgreich war, und wenn sie nicht tatsächlich Griff eine Ausnahme, Sie fangen es nicht ein. Dieses Protokoll ermöglicht es auch, den Fall zu behandeln, in dem eine Methode angeben muss, dass ein Fehler aufgetreten ist, ohne eine Ausnahme auszulösen. (Wenn Ihre Methoden nur dadurch fehlschlagen, dass eine Ausnahme ausgelöst wird, vereinfacht das das Protokoll. Wenn Sie mit vielen fehlerfreien Ausnahmezuständen außerhalb der fehlgeschlagenen Methode arbeiten müssen, haben Sie wahrscheinlich ein Designproblem habe noch nicht ausgearbeitet.)

Der Nachteil dieses Ansatzes ist, dass alle Methoden dieselben Argumente verwenden müssen. Ich habe mich für keines entschieden, mit der Erwartung, dass die Methoden, die ich ausgedruckt habe, am Ende die Klassenmitglieder manipulieren werden.

Die Oberseite dieses Ansatzes ist jedoch beträchtlich. Erstens können Sie dem Prozess Dutzende von Methoden hinzufügen, ohne dass es komplexer wird.

Sie können auch verrückt gehen und etwas tun:

def handle_exception(self, method, exception): 
    custom_handler_name = "handle_{0}_in_{1}".format(\ 
              exception.__class__.__name__, 
              method.__name__) 
    try: 
     custom_handler = self.__dict__[custom_handler_name] 
    except KeyError: 
     print "{name} ({msg}) in {method}.".format(
      name=exception.__class__.__name__, 
      msg=exception, 
      method=method.__name__) 
     return 
    custom_handler() 

def handle_AuthenticationError_in_authenticate(self): 
    print "Your login credentials are questionable." 

... obwohl an diesem Punkt, sage ich mir, vielleicht, „Selbst, bist du ziemlich hart auf den Befehl Muster arbeiten, ohne die Schaffung eine Kommandoklasse, vielleicht ist jetzt die Zeit."

Verwandte Themen