多个调用级别的异常处理
Exception handling over multiple calling levels
如何最好地处理引发异常的调用层次结构中的多个级别的方法,以便在出现致命错误时程序将退出(在显示错误对话框后)?
我基本上来自 Java。在那里我会简单地将任何方法声明为 throws Exception
,重新抛出它并在顶层的某个地方捕获它。
然而,Python不一样。我的 Python 代码基本上如下所示。
编辑: 添加了更简单的代码...
主入口函数(plugin.py
):
def main(catalog):
print "Executing main(catalog)... "
# instantiate generator
gen = JpaAnnotatedClassGenerator(options)
# run generator
try:
gen.generate_bar() # doesn't bubble up
except ValueError as error:
Utilities.show_error("Error", error.message, "OK", "", "")
return
... usually do the real work here if no error
JpaAnnotatedClassGenerator
class (engine.py
):
class JpaAnnotatedClassGenerator:
def generate_bar(self):
self.generate_value_error()
def generate_value_error(self):
raise ValueError("generate_value_error() raised an error!")
我想 return 给调用者一个异常,在它到达最外层 try-except
之前将抛回那个调用,以显示一个错误对话框和异常消息.
问题:
如何在 Python 中做到最好?我真的必须为每个被调用的方法重复 try-except
吗?
顺便说一句:我正在使用 Python 2.6.x,由于绑定到提供解释器的 MySQL Workbench,我无法升级(Python 3 在他们的升级列表中)。
如果您没有捕捉到异常,它会在调用堆栈中冒泡,直到有人捕捉到为止。如果没有人捕获到它,运行时将得到它并死去,并显示异常错误消息和完整的回溯。 IOW,您不必在任何地方明确地捕获并重新引发您的异常 - 这实际上会破坏存在异常的全部意义。实际上,尽管主要用于错误/意外情况,但异常首先是一种控制流工具,允许中断正常执行流并将控制(和一些信息)传递到调用堆栈中的任意位置。
从这个 POV 来看,您的代码似乎大部分都是正确的(注意:我没有费心阅读整篇文章,只是快速浏览了一下),除了(没有双关语缩进)有几点:
首先,您应该定义自己的特定异常 class(es) 而不是使用内置的 ValueError(如果对您有意义,您可以继承它),这样您就可以确定您只捕获了您期望的确切异常(很多层 "under" 您自己的代码可能会引发您没有预料到的 ValueError)。
然后,您可能(或不希望,取决于您的代码的使用方式)还想在 main()
函数中添加一个包罗万象的顶级处理程序,以便您可以正确记录(使用 logger
模块)所有错误并最终释放资源,在你的进程结束之前做一些清理等。
附带说明一下,您可能还想学习和使用正确的字符串格式,并且 - 如果性能至少是一个问题 - 避免像这样重复的常量调用:
elif AnnotationUtil.is_embeddable_table(table) and AnnotationUtil.is_secondary_table(table):
# ...
elif AnnotationUtil.is_embeddable_table(table):
# ...
elif AnnotationUtil.is_secondary_table(table):
# ...
考虑到 Python 非常动态的特性,编译器和运行时都不能安全地优化那些重复调用(方法可以在调用之间动态重新定义),所以你必须自己做。
编辑:
When trying to catch the error in the main() function, exceptions DON'T bubble up, but when I use this pattern one level deeper, bubbling-up seems to work.
您可以使用简单的 MCVE 轻松检查它是否正常工作:
def deeply_nested():
raise ValueError("foo")
def nested():
return deeply_nested()
def firstline():
return nested()
def main():
try:
firstline()
except ValueError as e:
print("got {}".format(e))
else:
print("you will not see me")
if __name__ == "__main__":
main()
It appears the software that supplies the Python env is somehow treating the main plugin file in a wrong way. Looks I will have to check the MySQL Workbench guys
呃......即使是嵌入式,机制预期仍应按预期工作 - 至少对于依赖于你的 main
函数的调用堆栈部分(无法判断上层发生了什么)调用栈)。但考虑到 MySQL 处理错误的方式(让你的数据被静默截断怎么样?),如果他们破解运行时以静默传递插件代码 xD
中的任何错误,我不会感到特别惊讶
出现错误没关系
Python的异常是未经检查的,这意味着您没有义务声明或处理它们。即使您知道某些事情可能会引发,也只有在您打算对其进行处理时才捕获错误。拥有异常透明层很好,当异常通过它们时优雅地中止:
def logged_get(map: dict, key: str):
result = map[key] # this may raise, but there is no state to corrupt
# the following is not meaningful if an exception occurred
# it is fine for it to be skipped by the exception bubbling up
print(map, '[%s]' % key, '=>', result)
return result
在这种情况下,logged_get
将简单地转发由查找引发的任何 KeyError
(和其他)。
如果外部调用者知道如何处理错误,它就可以这样做。
所以,按照您的方式调用 self.create_collection_embeddable_class_stub
。
错误终止应用程序是可以的
即使没有任何东西可以处理错误,解释器也会处理。你会得到一个堆栈跟踪,显示哪里出了问题。这种致命错误 "only happens if there is a bug" 可以 "safely" 冒泡以显示出了什么问题。
事实上,退出解释器和断言也是使用这种机制。
>>> assert 2 < 1, "This should never happen"
Traceback (most recent call last):
File "<string>", line 1, in <module>
AssertionError: This should never happen
对于许多服务,您甚至可以在部署中使用它 - 例如,systemd
会记录 Linux 系统服务。仅当安全问题或用户无法处理错误时才尝试抑制外部错误。
用精确误差就好了
由于未检查异常,您可以使用任意多个 而不会 使您的 API 过度紧张。这允许使用表示不同级别问题的自定义错误:
class DBProblem(Exception):
"""Something is wrong about our DB..."""
class DBEntryInconsistent(DBProblem):
"""A single entry is broken"""
class DBInconsistent(DBProblem):
"""The entire DB is foobar!"""
通常最好不要重复使用内置错误,除非您的用例确实符合它们的含义。这允许在需要时精确地处理错误:
try:
gen.generate_classes(catalog)
except DBEntryInconsistent:
logger.error("aborting due to corrupted entry")
sys.exit(1)
except DBInconsistent as err:
logger.error("aborting due to corrupted DB")
Utility.inform_db_support(err)
sys.exit(1)
# do not handle ValueError, KeyError, MemoryError, ...
# they will show up as a stack trace
如何最好地处理引发异常的调用层次结构中的多个级别的方法,以便在出现致命错误时程序将退出(在显示错误对话框后)?
我基本上来自 Java。在那里我会简单地将任何方法声明为 throws Exception
,重新抛出它并在顶层的某个地方捕获它。
然而,Python不一样。我的 Python 代码基本上如下所示。
编辑: 添加了更简单的代码...
主入口函数(plugin.py
):
def main(catalog):
print "Executing main(catalog)... "
# instantiate generator
gen = JpaAnnotatedClassGenerator(options)
# run generator
try:
gen.generate_bar() # doesn't bubble up
except ValueError as error:
Utilities.show_error("Error", error.message, "OK", "", "")
return
... usually do the real work here if no error
JpaAnnotatedClassGenerator
class (engine.py
):
class JpaAnnotatedClassGenerator:
def generate_bar(self):
self.generate_value_error()
def generate_value_error(self):
raise ValueError("generate_value_error() raised an error!")
我想 return 给调用者一个异常,在它到达最外层 try-except
之前将抛回那个调用,以显示一个错误对话框和异常消息.
问题:
如何在 Python 中做到最好?我真的必须为每个被调用的方法重复 try-except
吗?
顺便说一句:我正在使用 Python 2.6.x,由于绑定到提供解释器的 MySQL Workbench,我无法升级(Python 3 在他们的升级列表中)。
如果您没有捕捉到异常,它会在调用堆栈中冒泡,直到有人捕捉到为止。如果没有人捕获到它,运行时将得到它并死去,并显示异常错误消息和完整的回溯。 IOW,您不必在任何地方明确地捕获并重新引发您的异常 - 这实际上会破坏存在异常的全部意义。实际上,尽管主要用于错误/意外情况,但异常首先是一种控制流工具,允许中断正常执行流并将控制(和一些信息)传递到调用堆栈中的任意位置。
从这个 POV 来看,您的代码似乎大部分都是正确的(注意:我没有费心阅读整篇文章,只是快速浏览了一下),除了(没有双关语缩进)有几点:
首先,您应该定义自己的特定异常 class(es) 而不是使用内置的 ValueError(如果对您有意义,您可以继承它),这样您就可以确定您只捕获了您期望的确切异常(很多层 "under" 您自己的代码可能会引发您没有预料到的 ValueError)。
然后,您可能(或不希望,取决于您的代码的使用方式)还想在 main()
函数中添加一个包罗万象的顶级处理程序,以便您可以正确记录(使用 logger
模块)所有错误并最终释放资源,在你的进程结束之前做一些清理等。
附带说明一下,您可能还想学习和使用正确的字符串格式,并且 - 如果性能至少是一个问题 - 避免像这样重复的常量调用:
elif AnnotationUtil.is_embeddable_table(table) and AnnotationUtil.is_secondary_table(table):
# ...
elif AnnotationUtil.is_embeddable_table(table):
# ...
elif AnnotationUtil.is_secondary_table(table):
# ...
考虑到 Python 非常动态的特性,编译器和运行时都不能安全地优化那些重复调用(方法可以在调用之间动态重新定义),所以你必须自己做。
编辑:
When trying to catch the error in the main() function, exceptions DON'T bubble up, but when I use this pattern one level deeper, bubbling-up seems to work.
您可以使用简单的 MCVE 轻松检查它是否正常工作:
def deeply_nested():
raise ValueError("foo")
def nested():
return deeply_nested()
def firstline():
return nested()
def main():
try:
firstline()
except ValueError as e:
print("got {}".format(e))
else:
print("you will not see me")
if __name__ == "__main__":
main()
It appears the software that supplies the Python env is somehow treating the main plugin file in a wrong way. Looks I will have to check the MySQL Workbench guys
呃......即使是嵌入式,机制预期仍应按预期工作 - 至少对于依赖于你的 main
函数的调用堆栈部分(无法判断上层发生了什么)调用栈)。但考虑到 MySQL 处理错误的方式(让你的数据被静默截断怎么样?),如果他们破解运行时以静默传递插件代码 xD
出现错误没关系
Python的异常是未经检查的,这意味着您没有义务声明或处理它们。即使您知道某些事情可能会引发,也只有在您打算对其进行处理时才捕获错误。拥有异常透明层很好,当异常通过它们时优雅地中止:
def logged_get(map: dict, key: str):
result = map[key] # this may raise, but there is no state to corrupt
# the following is not meaningful if an exception occurred
# it is fine for it to be skipped by the exception bubbling up
print(map, '[%s]' % key, '=>', result)
return result
在这种情况下,logged_get
将简单地转发由查找引发的任何 KeyError
(和其他)。
如果外部调用者知道如何处理错误,它就可以这样做。
所以,按照您的方式调用 self.create_collection_embeddable_class_stub
。
错误终止应用程序是可以的
即使没有任何东西可以处理错误,解释器也会处理。你会得到一个堆栈跟踪,显示哪里出了问题。这种致命错误 "only happens if there is a bug" 可以 "safely" 冒泡以显示出了什么问题。
事实上,退出解释器和断言也是使用这种机制。
>>> assert 2 < 1, "This should never happen"
Traceback (most recent call last):
File "<string>", line 1, in <module>
AssertionError: This should never happen
对于许多服务,您甚至可以在部署中使用它 - 例如,systemd
会记录 Linux 系统服务。仅当安全问题或用户无法处理错误时才尝试抑制外部错误。
用精确误差就好了
由于未检查异常,您可以使用任意多个 而不会 使您的 API 过度紧张。这允许使用表示不同级别问题的自定义错误:
class DBProblem(Exception):
"""Something is wrong about our DB..."""
class DBEntryInconsistent(DBProblem):
"""A single entry is broken"""
class DBInconsistent(DBProblem):
"""The entire DB is foobar!"""
通常最好不要重复使用内置错误,除非您的用例确实符合它们的含义。这允许在需要时精确地处理错误:
try:
gen.generate_classes(catalog)
except DBEntryInconsistent:
logger.error("aborting due to corrupted entry")
sys.exit(1)
except DBInconsistent as err:
logger.error("aborting due to corrupted DB")
Utility.inform_db_support(err)
sys.exit(1)
# do not handle ValueError, KeyError, MemoryError, ...
# they will show up as a stack trace