我想在不退出的情况下捕获和记录异常,例如,

try:
    do_stuff()
except Exception as err:
    print(Exception, err)
    # I want to print the entire traceback here,
    # not just the exception name and details

我想打印与抛出异常时打印的完全相同的输出,而不使用try/,只是拦截异常,并且我不希望它退出程序。


当前回答

首先,不要使用打印来记录日志,有一个稳定的、经过验证的、经过深思熟虑的stdlib模块可以做到这一点:日志。你绝对应该用它来代替。

其次,当有原生的简单方法时,不要试图用不相关的工具搞得一团糟。下面就是:

log = logging.getLogger(__name__)

try:
    call_code_that_fails()
except MyError:
    log.exception('Any extra info you want to see in your logs')

就是这样。现在你完成了。

为任何对事物的工作原理感兴趣的人提供解释

什么日志。异常实际上所做的只是对日志的调用。error(即记录级别为error的事件),然后打印traceback。

为什么更好呢?

这里有一些注意事项:

它刚刚好; 这很简单; 这很简单。

为什么没有人应该使用exc_info=True的跟踪或调用记录器,或者用sys.exc_info弄脏他们的手?

嗯,就是因为!它们存在的目的各不相同。例如,traceback。Print_exc的输出与解释器本身产生的回溯略有不同。如果你使用它,你会让任何阅读你日志的人感到困惑,他们会用头去撞你的日志。

将exc_info=True传递给日志调用是不合适的。但是,当捕获可恢复的错误并且你想用回溯记录它们(使用,例如INFO级别)时,它是有用的,因为log。exception只产生一个级别的日志- ERROR。

而且你绝对应该避免和sys搞混。Exc_info。它不是一个公共接口,而是一个内部接口——如果你明确知道自己在做什么,你就可以使用它。它不仅仅用于打印异常。

其他回答

您需要将try/except放在可能发生错误的最内层循环中,即。

for i in something:
    for j in somethingelse:
        for k in whatever:
            try:
                something_complex(i, j, k)
            except Exception, e:
                print e
        try:
            something_less_complex(i, j)
        except Exception, e:
            print e

... 等等

换句话说,您需要将可能在try/中失败的语句包装在尽可能具体的内部循环中。

其他一些答案已经指出了回溯模块。

请注意,使用print_exc,在某些极端情况下,您将无法获得您所期望的结果。在Python 2.x中:

import traceback

try:
    raise TypeError("Oups!")
except Exception, err:
    try:
        raise TypeError("Again !?!")
    except:
        pass

    traceback.print_exc()

...将显示上一个异常的回溯:

Traceback (most recent call last):
  File "e.py", line 7, in <module>
    raise TypeError("Again !?!")
TypeError: Again !?!

如果你真的需要访问原始的回溯,一个解决方案是将exc_info返回的异常信息缓存到一个局部变量中,并使用print_exception显示它:

import traceback
import sys

try:
    raise TypeError("Oups!")
except Exception, err:
    try:
        exc_info = sys.exc_info()

        # do you usefull stuff here
        # (potentially raising an exception)
        try:
            raise TypeError("Again !?!")
        except:
            pass
        # end of useful stuff


    finally:
        # Display the *original* exception
        traceback.print_exception(*exc_info)
        del exc_info

生产:

Traceback (most recent call last):
  File "t.py", line 6, in <module>
    raise TypeError("Oups!")
TypeError: Oups!

但这也有一些陷阱:

From the doc of sys_info: Assigning the traceback return value to a local variable in a function that is handling an exception will cause a circular reference. This will prevent anything referenced by a local variable in the same function or by the traceback from being garbage collected. [...] If you do need the traceback, make sure to delete it after use (best done with a try ... finally statement) but, from the same doc: Beginning with Python 2.2, such cycles are automatically reclaimed when garbage collection is enabled and they become unreachable, but it remains more efficient to avoid creating cycles.


另一方面,通过允许你访问与异常相关的回溯,Python 3产生了一个不那么令人惊讶的结果:

import traceback

try:
    raise TypeError("Oups!")
except Exception as err:
    try:
        raise TypeError("Again !?!")
    except:
        pass

    traceback.print_tb(err.__traceback__)

... 将显示:

  File "e3.py", line 4, in <module>
    raise TypeError("Oups!")

这是我把错误写在日志文件和控制台的解决方案:

import logging, sys
import traceback
logging.basicConfig(filename='error.log', level=logging.DEBUG)

def handle_exception(exc_type, exc_value, exc_traceback):
    if issubclass(exc_type, KeyboardInterrupt):
        sys.__excepthook__(exc_type, exc_value, exc_traceback)
        return
    exc_info=(exc_type, exc_value, exc_traceback)
    logging.critical("\nDate:" + str(datetime.datetime.now()), exc_info=(exc_type, exc_value, exc_traceback))
    print("An error occured, check error.log to see the error details")
    traceback.print_exception(*exc_info)


sys.excepthook = handle_exception

要获得精确的堆栈跟踪(作为字符串),如果没有try/except进行跨步处理,则会引发该字符串,只需将其放置在捕获违规异常的except块中。

desired_trace = traceback.format_exc(sys.exc_info())

下面是如何使用它(假设定义了flaky_func,并且log调用您最喜欢的日志系统):

import traceback
import sys

try:
    flaky_func()
except KeyboardInterrupt:
    raise
except Exception:
    desired_trace = traceback.format_exc(sys.exc_info())
    log(desired_trace)

捕获并重新引发KeyboardInterrupts是个好主意,这样您仍然可以使用Ctrl-C终止程序。日志记录不在这个问题的范围之内,但是日志记录是一个很好的选择。sys和traceback模块的文档。

如果你正在调试,只想查看当前的堆栈跟踪,你可以简单地调用:

traceback.print_stack ()

没有必要为了再次捕获异常而手动引发异常。