如何在Python中查找exit code或原因,当调用atexit回调时?

30
我想知道Python脚本是否正确终止。为此,我正在使用atexit。但问题是我不知道如何区分atexit是使用sys.exit(0)、非零值或异常调用的。
原因是:如果程序正常结束,它什么也不会做,但如果程序由于异常或返回非零错误代码(退出状态)而结束,我想触发一些操作。
如果你想知道为什么我不使用try/finally,那是因为我想为十几个导入一个公共模块的脚本添加相同的行为。我想在被导入的模块中添加atexit()函数并免费获得所有脚本的这种行为,而不是修改它们中的所有脚本。
2个回答

25

你可以使用 sys.excepthook 并通过猴子补丁 sys.exit() 来解决这个问题:

import atexit
import sys

class ExitHooks(object):
    def __init__(self):
        self.exit_code = None
        self.exception = None

    def hook(self):
        self._orig_exit = sys.exit
        sys.exit = self.exit
        sys.excepthook = self.exc_handler

    def exit(self, code=0):
        self.exit_code = code
        self._orig_exit(code)

    def exc_handler(self, exc_type, exc, *args):
        self.exception = exc

hooks = ExitHooks()
hooks.hook()

def foo():
    if hooks.exit_code is not None:
        print("death by sys.exit(%d)" % hooks.exit_code)
    elif hooks.exception is not None:
        print("death by exception: %s" % hooks.exception)
    else:
        print("natural death")
atexit.register(foo)

# test
sys.exit(1)

@sorin:对我来说它很好用。我将钩子函数封装在一个类中(不改变功能),并添加了sys.exit(1)。在Python 2.7上,它为我打印出了sys.exit(1) - Niklas B.
@sorin:你可能在patched_exit函数中忘记了一个global exit_code吗?我一开始也忘了,但是在发布答案后立即进行了编辑。 - Niklas B.
1
@sorin:我只是为了避免全局变量才转换到类,机制完全相同。如果您检查旧版本,它应该能够正常工作(至少对我来说是这样)。 - Niklas B.
4
似乎这会导致回溯输出(stdout/err)消失。有没有办法保留回溯输出,只添加打印语句呢? - e9t
@e9t 我来晚了,但刚开始使用这个并遇到了您的问题。您需要捕获原始的excepthook并调用它。基本上,这个解决方案对于sys.exit是这样做的,但是对于sys.excepthook也适用。 - Kyle Hannon

1
这个对 Niklas. B. 代码的调整展示了堆栈跟踪。
import atexit
import sys

class ExitHooks(object):
    def __init__(self):
        self.exit_code = None
        self.exception = None

    def hook(self):
        self._orig_exit = sys.exit
        self._orig_exc_handler = self.exc_handler
        sys.exit = self.exit
        sys.excepthook = self.exc_handler

    def exit(self, code=0):
        self.exit_code = code
        self._orig_exit(code)

    def exc_handler(self, exc_type, exc, *args):
        self.exception = exc
        self._orig_exc_handler(self, exc_type, exc, *args)

def exit_handler():
    if hooks.exit_code is not None:
        print("death by sys.exit(%d)" % hooks.exit_code)
    elif hooks.exception is not None:
        print("death by exception: %s" % hooks.exception)
    else:
        print("natural death")

hooks = ExitHooks()
hooks.hook()
atexit.register(exit_handler)

# test
sys.exit(1)

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