Python中处理异常的正确方法?
我搜索了其他帖子,因为我觉得这是一个相当常见的问题,但我发现的所有其他Python异常问题都没有反映我的问题 在这里,我将尽可能具体,所以我将给出一个直接的例子。请不要发布任何解决这个特定问题的方法。我并不特别感兴趣的是,你如何能用xyz发送一封更好的电子邮件。 我想知道您通常如何处理依赖的、容易出错的语句 我的问题是,如何很好地处理相互依赖的异常,这意味着: 只有在第一步成功的情况下,才尝试下一步,以此类推。还有一个标准是:必须捕获所有异常,该代码必须健壮 请举例说明:Python中处理异常的正确方法?,python,exception,Python,Exception,我搜索了其他帖子,因为我觉得这是一个相当常见的问题,但我发现的所有其他Python异常问题都没有反映我的问题 在这里,我将尽可能具体,所以我将给出一个直接的例子。请不要发布任何解决这个特定问题的方法。我并不特别感兴趣的是,你如何能用xyz发送一封更好的电子邮件。 我想知道您通常如何处理依赖的、容易出错的语句 我的问题是,如何很好地处理相互依赖的异常,这意味着: 只有在第一步成功的情况下,才尝试下一步,以此类推。还有一个标准是:必须捕获所有异常,该代码必须健壮 请举例说明: try: se
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
这在我看来非常不和谐,错误处理代码是实际业务代码的三倍,但另一方面,我如何处理几个相互依赖的语句,这意味着语句1是语句2的先决条件,依此类推
我还对适当的资源清理感兴趣,即使Python自己也能处理
谢谢,汤姆,为什么不试一下:布洛克?这样,如果捕获到任何异常,您将一直转到exception。而且,只要不同步骤的所有异常都不同,您总是可以判断引发异常的部分。一般来说,您希望使用尽可能少的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
这不是很好,因为您必须在多个位置杀死服务器,但是现在我们可以在不同的位置以不同的方式处理特定的异常类型,而不需要维护任何额外的状态。我喜欢David的回答,但是如果您被困在服务器异常上,您还可以检查服务器是否为无或状态。我把这个方法展平了一点,它仍然是一个非音速的方法,但在底部的逻辑中更具可读性
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
只需使用一个试块即可。这正是他们想要的 设计用于:仅当上一条语句 语句未引发异常。在资源清理方面,, 如果资源需要清理,也许您可以检查它 (例如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
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__)
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."
错误处理代码超过业务代码的情况并不少见。正确的错误处理可能很复杂。
但为了提高可维护性,它有助于将业务代码与错误处理代码分开。不使用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()
这是完全可读的,而且很像蟒蛇
另一种方法是,与其担心具体的实现,不如决定代码的外观,例如
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")
如果是我,我可能会这样做:
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,如果建立了初始连接,服务器连接将被正确清理。我将尝试以下方法:
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
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__)
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."
所有方法(包括send_message
)都遵循相同的协议:如果成功,它们将返回True,除非它们实际处理异常,否则不会捕获异常。该协议还可以处理方法需要指示失败而不引发异常的情况。(如果方法失败的唯一方式是引发异常,这将简化协议。如果必须处理失败方法之外的许多非异常失败状态,则可能存在尚未解决的设计问题。)
这种方法的缺点是所有方法都必须使用相同的参数。我选择了none,希望我所剔除的方法最终能够操纵类成员
不过,这种方法的好处是可观的。首先,您可以向流程中添加几十种方法,而不会使send\u message
变得更复杂
你也可以疯狂地做这样的事情:
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
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__)
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."
…虽然在这一点上,我可能会对自己说,“赛尔夫,你在不创建命令类的情况下非常努力地使用命令模式。也许现在是时候了。”因为一个大的try块不会让你有机会管理资源。例如:语句1工作,并打开了一个文件,但语句2抛出异常。您无法在单个finally块中处理该问题,因为您永远不知道资源是否已实际分配。此外,某些步骤可能会触发相同的异常,因此无法确定以后发生了什么错误,也无法打印错误,因为您不确定哪条语句实际失败。遗憾的是,我无法依赖为我将使用的所有操作定义的enter和exit方法,因此,使用可能永远不起作用,但您始终可以构建自己的包装器——它们足够轻,特别是如果您使用装饰器,并且如果它们可以为您制作,那么它是值得的