在Python中处理异常的正确方法?

时间:2009-06-12 00:45:51

标签: python exception

我搜索了其他帖子,因为我觉得这是一个相当常见的问题,但我发现的所有其他Python异常问题都没有反映我的问题。

我会尽量在这里具体说明,所以我将举一个直接的例子。并且pleeeeease不会针对此特定问题发布任何变通方法。我对你如何使用xyz发送更好的电子邮件并不感兴趣。 我想知道你如何处理依赖的,容易出错的陈述。

我的问题是,如何很好地处理异常,相互依赖的异常,意思是: 只有第一步成功,尝试下一步,依此类推。还有一个标准是:必须捕获所有异常,此代码必须是健壮的。

供您考虑,例如:

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

这看起来非常简单,错误处理代码是实际业务代码的三倍,但另一方面,我如何处理几个相互依赖的语句,这意味着statement1是statement2的先决条件,依此类推?

我也对正确的资源清理感兴趣,即使Python可以自己管理它。

谢谢,汤姆

7 个答案:

答案 0 :(得分:24)

而不是使用try / except的else块,只需在错误时返回:

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()

那是完全可读的和Pythonic ..

另一种方法是,而不是担心具体的实现,决定你希望代码的外观,例如..

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!"

然后编写message()方法的代码,捕获您期望的任何错误,并提出自己的自定义错误,并处理它相关的位置。你的课可能看起来像..

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")

答案 1 :(得分:12)

通常,您希望尽可能少地使用try块,通过它们抛出的异常类型来区分失败条件。例如,这是我对您发布的代码的重构:

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

在这里,我们使用smtplib.SMTP(),server.login()和server.sendmail()都抛出不同的异常以展平try-catch块树的事实。在finally块中,我们明确地测试服务器以避免在nil对象上调用quit()。

我们还可以使用三个顺序 try-catch块,如果存在需要单独处理的重叠异常情况,则在异常条件下返回False:

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

这不是很好,因为你必须在多个地方杀死服务器,但现在我们可以在不同的地方以不同的方式处理特定的异常类型而不保持任何额外的状态。

答案 2 :(得分:3)

如果是我,我可能会做以下事情:

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

捕获并调试所有错误,成功时返回值== True,如果建立初始连接,则正确清理服务器连接。

答案 3 :(得分:1)

只需使用一个try-block就可以了。这正是他们的意思 设计用于:只执行前一个语句 声明没有抛出异常。至于资源清理, 也许你可以检查资源是否需要清理 (例如myfile.is_open(),...)这确实增加了一些额外条件,但是 它们只会在特殊情况下执行。处理此案 您可以出于同样的原因提出相同的异常 应该能够从异常中检索原因。

我建议这样的代码:

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

错误处理代码超出业务代码并不罕见。正确的错误处理可能很复杂。 但是为了提高可维护性,它有助于将业务代码与错误处理代码分开。

答案 4 :(得分:1)

我会尝试这样的事情:

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__)

所有方法(包括send_message)都遵循相同的协议:如果成功则返回True,除非它们实际上处理异常,否则它们不会捕获它。该协议还可以处理方法需要指示失败而不引发异常的情况。 (如果你的方法失败的唯一方法是引发异常,这会简化协议。如果你不得不处理失败的方法之外的很多非异常失败状态,你可能有一个设计问题,你尚未解决。)

这种方法的缺点是所有方法都必须使用相同的参数。我选择了没有,期望我已经删除的方法最终会操纵类成员。

这种方法的好处是相当可观的。首先,您可以在流程中添加许多方法,而send_message不会变得更复杂。

你也可以发疯并做这样的事情:

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."

......虽然在那一点上,我可能会对自己说,“自我,你在没有创建Command类的情况下非常努力地使用Command模式。也许现在是时候了。”

答案 5 :(得分:0)

为什么不尝试一下:阻止?这样,如果发现任何异常,你将一直走到except。只要不同步骤的所有异常都不同,您就可以随时告诉它触发异常的部分。

答案 6 :(得分:0)

我喜欢David的回答,但是如果您遇到服务器异常,您还可以检查服务器是否为None或状态。我把这个方法弄平了一点,它仍然是一个但却是单声道的,但在底部的逻辑中更具可读性。

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