如何确定发生了什么类型的异常?

361

some_function()在执行过程中引发了异常,因此程序跳转到except块:

try:
    some_function()
except:
    print("exception happened!")

我该如何查看导致异常的原因?


11
永远不要使用裸的 except:(没有裸的 raise),除非在整个程序中只有一次,最好也不要使用。请注意,此处的“裸”指的是未指定异常类型的情况。 - Mike Graham
如果您使用多个except子句,则无需检查异常类型,这通常是根据特定的异常类型采取相应措施的做法。 - Rik Poggi
3
如果你关心异常的类型,那是因为你已经考虑过哪些异常类型在逻辑上可能会发生。 - Karl Knechtel
7
except 块内,可以通过 sys.exc_info() 函数获取异常信息 – 该函数返回一个包含当前处理的异常相关信息的三元组。 - Piotr Dobrogost
相关:如何在Python中打印错误? - Stevoisiak
我该如何填写这里的空白?except __ as e。也就是说,我该如何获取类/类型以便捕获它?顺便提一下,我正在使用Databricks,所以当我尝试打印type(e)时,我看到的是:<class 'dbutils.DBUtils.FSHandler.prettify_exception_message.<locals>.f_with_exception_handling.<locals>.ExecutionError'>。 - information_interchange
16个回答

580
The other answers all point out that you should not catch generic exceptions, but no one seems to want to tell you why, which is essential to understanding when you can break the "rule". Here is an explanation. Basically, it's so that you don't hide: So as long as you take care to do none of those things, it's OK to catch the generic exception. For instance, you could provide information about the exception to the user another way, like:
  • Present exceptions as dialogs in a GUI
  • Transfer exceptions from a worker thread or process to the controlling thread or process in a multithreading or multiprocessing application
So how to catch the generic exception? There are several ways. If you just want the exception object, do it like this:
try:
    someFunction()
except Exception as ex:
    template = "An exception of type {0} occurred. Arguments:\n{1!r}"
    message = template.format(type(ex).__name__, ex.args)
    print message

确保以难以忽略的方式向用户传达信息!如果消息深埋在大量其他消息中,则像上面所示地打印可能不足够。未能引起用户的注意相当于吞噬所有异常,如果你在阅读本页答案后应该有一个印象,那就是这不是一件好事。通过使用 raise 语句结束 except 块,可以通过透明地重新引发被捕获的异常来解决问题。
上述方法与仅使用 except: 的区别在于:
  • 裸的 except: 不会给出可检查的异常对象。
  • 异常 SystemExitKeyboardInterruptGeneratorExit 不会被上述代码捕获,这通常是你想要的。请参见 exception hierarchy
如果您希望获得与未捕获异常相同的堆栈跟踪,可以使用以下方式(仍在except子句中):
import traceback
print traceback.format_exc()

如果您使用logging模块,可以像这样将异常(以及消息)打印到日志中:
import logging
log = logging.getLogger()
log.exception("Message for you, sir!")

如果您想深入挖掘并检查堆栈、变量等,请在 except 块中使用 pdb 模块的 post_mortem 函数:
import pdb
pdb.post_mortem()

我发现这个方法在查找错误时非常有用。

2
traceback.print_exc()会做与您更复杂的"".join-thing相同的事情,我想。 - Gurgeh
2
@Gurgeh 是的,但我不知道他是想打印出来还是保存到文件中,或者记录下来,或者用其他方式处理。 - Lauritz V. Thaulow
我没有点踩,但我认为这是因为你应该在开头放置一个巨大的警告,说“你不应该需要任何这样的东西,但这里是如何完成的”。也许是因为你建议捕获通用异常。 - Rik Poggi
15
@Rik 我认为你可能需要_全部_内容。例如,如果你有一个带有GUI和后端的程序,并且你想将来自后端的所有异常作为GUI消息呈现,而不是让程序以堆栈跟踪退出。在这种情况下,你应该捕捉_Generic_ Exception,为对话框创建回溯文本,也要记录异常,如果处于调试模式,则进入死后调试。 - Lauritz V. Thaulow
26
天真的想法。当你需要从别人的代码中捕获异常,而且不知道会抛出什么异常时,有许多合理的情况需要这样做。 - stackoverflowuser2010
1
这个事后分析的技巧帮助我缩小了一个 objc.error 异常类型,这种异常有点奇怪。 - jxramos

157

获取异常对象所属类的名称:

e.__class__.__name__

使用print_exc()函数还会打印堆栈跟踪信息,这是任何错误消息中必要的信息。

像这样:

from traceback import print_exc

class CustomException(Exception): pass

try:
    raise CustomException("hi")
except Exception as e:
    print ('type is:', e.__class__.__name__)
    print_exc()
    # print("exception happened!")
您将会得到如下输出:
type is: CustomException
Traceback (most recent call last):
  File "exc.py", line 7, in <module>
    raise CustomException("hi")
CustomException: hi

在打印和分析后,代码可以决定不处理异常并执行raise

from traceback import print_exc

class CustomException(Exception): pass

def calculate():
    raise CustomException("hi")

try:
    calculate()
except CustomException as e:
    # here do some extra steps in case of CustomException
    print('custom logic doing cleanup and more')
    # then re raise same exception
    raise

输出:

custom logic doing cleanup and more

解释器输出异常:

Traceback (most recent call last):
  File "test.py", line 9, in <module>
    calculate()
  File "test.py", line 6, in calculate
    raise CustomException("hi")
__main__.CustomException: hi

使用raise语句引发异常会导致原始异常继续向上调用堆栈传播。(注意可能的陷阱)如果引发新的异常,则将携带新的(较短的)堆栈跟踪。

from traceback import print_exc

class CustomException(Exception):
    def __init__(self, ok):
        self.ok = ok

def calculate():
    raise CustomException(False)

try:
    calculate()
except CustomException as e:
    if not e.ok:
        # Always use `raise` to rethrow exception
        # following is usually mistake, but here we want to stress this point
        raise CustomException(e.ok)
    print("handling exception")

输出:

Traceback (most recent call last):
  File "test.py", line 13, in <module>
    raise CustomException(e.message)
__main__.CustomException: hi    

注意,回溯信息中并没有包含来自第9行的calculate()函数,而这个函数是导致原始异常e的根源。


1
e.__class__.__name__和上面的答案建议的type(e).__name__是一样的吗? - information_interchange
1
@信息交流 是的。随着时间的推移,问题和被接受的答案内容已经完全改变。可惜的是,其他参与者没有收到SO机器的通知 :( - Alex
请直接使用except CustomException,不要之后再进行类型检查。 - DSchmidt
@DSchmidt,“不要这样做”并不是任何问题的好答案,请看清问题的表述方式。 - Alex
@Alex,我不会回答任何问题。我只是评论此答案的当前状态。它使用if e.__class__ == CustomException而不是更简洁的except CustomException - DSchmidt
显示剩余5条评论

16

通常,您不应使用 try: ... except 来捕获所有可能的异常,因为这样过于宽泛。只捕获那些由于某种原因预计会发生的异常即可。如果您确实必须这样做,例如在调试时想要了解有关某个问题的更多信息,那么应该采取以下措施:

try:
    ...
except Exception as ex:
    print ex # do whatever you want for debugging.
    raise    # re-raise exception.

22
“never”在这里的使用从未如此错误。我在很多东西周围都使用“try: ... except Exception:”,例如使用网络相关库或数据处理器可能会收到奇怪的信息。当然,我也有适当的日志记录。这对于允许程序在输入数据有单个错误的情况下继续运行至关重要。 - thnee
4
曾经尝试过使用smtplib发送电子邮件时捕获可能引发的所有异常吗? - linusg
2
可能有一些特殊情况需要捕获所有异常,但通常情况下,您应该只捕获您预期的异常,以免意外隐藏您没有预料到的错误。当然,良好的日志记录也是一个好主意。 - hochl
5
捕获所有异常是完全合理的。如果你在调用第三方库时不知道会抛出哪些异常,在这种情况下,唯一的选择就是捕获所有异常,例如将它们记录在文件中。 - stackoverflowuser2010
好的,你说得对,我会重新表述我的答案,以明确万能捕获的有效用例。 - hochl
显示剩余2条评论

13
大多数答案都指向了except (…) as (…):语法(确实如此),但同时没有人想谈论房间里的大象,该大象就是sys.exc_info()函数。 来自sys模块的文档(强调是我的):

该函数返回一个三元组,其中包含有关当前正在处理的异常的信息。
(…)
如果堆栈上没有任何地方正在处理异常,则返回一个包含三个None值的元组。否则,返回的值为(type,value,traceback)。它们的含义是: type获取正在处理的异常的类型 (BaseException子类); value获取异常实例(异常类型的实例);traceback获取一个追溯对象(请参见参考手册),该对象封装了最初出现异常的调用堆栈。

我认为sys.exc_info()可以被视为对最初问题的直接答案:“我怎样才能知道发生了什么类型的异常?”

2
对我来说,这是正确的答案,因为它解决了异常发生的问题,所以我应该用什么代替裸露的 except。只是为了完整起见,exctype, value = sys.exc_info()[:2] 将告诉您异常类型,然后可以在 except 中使用它。 - Ondrej Burkert

10
这些回答对于调试很好,但是为了编程化地测试异常,isinstance(e, SomeException) 很方便,因为它也可以测试 SomeException 的子类,这样你就能够创建适用于异常层次结构的功能。

8

除非somefunction是一个非常糟糕的遗留函数,否则您不应该需要您所要求的内容。

使用多个except子句以不同方式处理不同的异常:

try:
    someFunction()
except ValueError:
    # do something
except ZeroDivision:
    # do something else

重点是不要捕获通用的异常,只需要捕获需要的异常。我相信您不希望掩盖意外错误或漏洞。


13
如果您正在使用第三方库,可能不知道其中会引发哪些异常。那么,您如何才能逐个捕获所有这些异常呢? - stackoverflowuser2010

7
在Python 2中,以下内容非常有用:
except Exception, exc:

    # This is how you get the type
    excType = exc.__class__.__name__

    # Here we are printing out information about the Exception
    print 'exception type', excType
    print 'exception msg', str(exc)

    # It's easy to reraise an exception with more information added to it
    msg = 'there was a problem with someFunction'
    raise Exception(msg + 'because of %s: %s' % (excType, exc))

使用exc.__class__.__name__已经在Alex的答案中提到了,建议使用-1。- https://dev59.com/8mkw5IYBdhLWcg3wirCs#9824060 - Piotr Dobrogost

4
使用 type 类和 as 语句。
try:#code
except Exception as e:
     m=type(e)
     #m is the class of the exception
     strm=str(m)
     #strm is the string of m

3

希望这能再帮上一点忙。

import sys
varExcepHandling, varExcepHandlingZer = 2, 0
try:
  print(varExcepHandling/varExcepHandlingZer)
except Exception as ex: 
  print(sys.exc_info())

'sys.exc_info()'会返回一个元组,如果你只想要异常类名,可以使用'sys.exc_info()[0]'
注意:如果您想查看所有的异常类,请输入dir(__builtin__)

1
将以下与编程有关的内容从英语翻译成中文。 仅返回已回答实际问题的响应文本。 - Bersan

1
这是我处理异常的方法。思路是在可能的情况下尝试解决问题,稍后再添加更理想的解决方案。不要在生成异常的代码中解决问题,否则该代码将失去原始算法的追踪,应该简明扼要地编写。然而,传递解决问题所需的数据,并返回一个lambda表达式,以防无法在生成代码之外解决该问题。
path = 'app.p'

def load():
    if os.path.exists(path):
        try:
            with open(path, 'rb') as file:
                data = file.read()
                inst = pickle.load(data)
        except Exception as e:
            inst = solve(e, 'load app data', easy=lambda: App(), path=path)()
    else:
        inst = App()
    inst.loadWidgets()

# e.g. A solver could search for app data if desc='load app data'
def solve(e, during, easy, **kwargs):
    class_name = e.__class__.__name__
    print(class_name + ': ' + str(e))
    print('\t during: ' + during)
    return easy

目前,由于我不想与我的应用程序目的有关联地思考,所以我没有添加任何复杂的解决方案。但是,在未来,当我更多地了解可能的解决方案(因为应用程序的设计更多)时,我可以添加一个按during索引的解决方案字典。

在所示示例中,一种解决方案可能是查找存储在其他地方的应用程序数据,例如如果错误删除了“app.p”文件。

目前,编写异常处理程序并不是一个明智的想法(我们还不知道最好的解决方法,因为应用程序设计将会发展),我们只需返回简单的修复方式,就像我们第一次运行应用程序一样(在这种情况下)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接