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

Tom*_*Tom 27 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
Run Code Online (Sandbox Code Playgroud)

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

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

谢谢,汤姆

dbr*_*dbr 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()
Run Code Online (Sandbox Code Playgroud)

这是完全可读和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!"
Run Code Online (Sandbox Code Playgroud)

然后编写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")
Run Code Online (Sandbox Code Playgroud)

  • +1我真的很喜欢你如何解决"库只使用一个例外的一切"问题 (4认同)

Dav*_*ler 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
Run Code Online (Sandbox Code Playgroud)

在这里,我们使用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
Run Code Online (Sandbox Code Playgroud)

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

  • 如上所述,关键是他们不会抛出个别例外,所以它不能轻易地被夷为平地.如果您的连接在您可以auth之前被中断,server.login和server.sendMail可能会抛出相同的异常("首先连接到服务器")但是正如我上面所说,我不是在寻找这个特定问题的解决方案.我对如何解决这个问题的一般方法更感兴趣.你的第二种方法基本上是我没有"其他"的代码.虽然我不得不承认它更漂亮;) (5认同)