python 退出执行_如何始终在Python中执行退出功能

python 退出执行

…or why atexit.register() and signal.signal() are evil UPDATE (2016-02-13): this recipe no longer handles SIGINT, SIGQUIT and SIGABRT as aliases for “application exit” because it was a bad idea. It only handles SIGTERM. Also it no longer support Windows because signal.signal() implementation is too different than POSIX.

…或者为什么atexit.register()和signal.signal()是邪恶的 更新(2016-02-13):此食谱不再将SIGINT,SIGQUIT和SIGABRT作为“应用程序退出”的别名来处理,因为这是一个坏主意 它仅处理SIGTERM。 而且它不再支持Windows,因为signal.signal()的实现与POSIX 太不同了。

Many people erroneously think that any function registered via atexit module is guaranteed to always be executed when the program terminates. You may have noticed this is not the case when, for example, you daemonize your app in production then try to stop it or restart it: the cleanup functions will not be executed. This is because functions registered wth atexit module are not called when the program is killed by a signal:

许多人错误地认为,通过atexit模块注册的任何功能都可以保证在程序终止时始终执行。 您可能已经注意到情况并非如此,例如,当您在生产环境中守护应用程序然后尝试将其停止或重新启动时:清理功能将不会执行。 这是因为当程序被信号杀死时, 不会调用通过atexit模块注册的函数:

import atexit, os, signal

@atexit.register
def cleanup():
    print("on exit")  # XXX this never gets printed

os.kill(os.getpid(), signal.SIGTERM)

It must be noted that the same thing would happen if instead of atexit.register() we would use a “finally” clause. It turns out the correct way to make sure the exit function is always called in case a signal is received is to register it via signal.signal(). That has a drawback though: in case a third-party module has already registered a function for that signal (SIGTERM or whatever), your new function will overwrite the old one:

必须注意的是,如果我们使用a“ finally”子句代替atexit.register(),将会发生相同的事情。 事实证明,确保在收到信号的情况下始终调用exit函数的正确方法是通过signal.signal()对其进行注册。 但是,这样做有一个缺点:如果第三方模块已经为该信号注册了一个函数(SIGTERM或其他),则您的新函数将覆盖旧的函数:

import os, signal

def old(*args):
    print("old")  # XXX this never gets printed

def new(*args):
    print("new")

signal.signal(signal.SIGTERM, old)
signal.signal(signal.SIGTERM, new)
os.kill(os.getpid(), signal.SIGTERM)

Also, we would still have to use atexit.register() so that the function is called also on “clean” interpreter exit and take into account other signals other than SIGTERM which would cause the process to terminate . This recipe attempts to address all these issues so that:

同样,我们仍然必须使用atexit.register(),以便在“干净”解释器出口也调用该函数, 并考虑到SIGTERM以外的其他信号,这会导致进程终止 。 本食谱试图解决所有这些问题,以便:

  •  the exit function is always executed  for all exit signals (SIGTERM, SIGINT, SIGQUIT, SIGABRT)  on SIGTERM and on “clean” interpreter exit.
  • any exit function(s) previously registered via atexit.register() or signal.signal() will be executed as well (after the new one). 
  • It must be noted that the exit function will never be executed in case of SIGKILL, SIGSTOP or os._exit().
  • 始终对SIGTERM和“干净”解释器出口上的 所有出口信号(SIGTERM,SIGINT,SIGQUIT,SIGABRT) 执行出口功能。
  • 先前通过atexit.register()signal.signal()注册的所有退出函数也将被执行(在新函数之后)。
  • 必须注意的是,对于SIGKILL,SIGSTOP或os._exit() ,退出函数将永远不会执行。

代码 ( The code)

import atexit
import os
import signal
import sys


if os.name != 'posix':
    raise ImportError("POSIX only")
_registered_exit_funs = set()
_executed_exit_funs = set()
_exit_signals = frozenset([signal.SIGTERM])


def register_exit_fun(fun, signals=_exit_signals):
    """Register a function which will be executed on "normal"
    interpreter exit or in case one of the `signals` is received
    by this process (differently from atexit.register()).

    Also, it makes sure to execute any previously registered
    via signal.signal().If any, it will be executed after `fun`.

    Functions which were already registered or executed via this
    function will be skipped.

    Exit function will not be executed on SIGKILL, SIGSTOP or
    os._exit(0).
    """
    def fun_wrapper():
        if fun not in _executed_exit_funs:
            try:
                fun()
            finally:
                _executed_exit_funs.add(fun)

    def signal_wrapper(signum=None, frame=None):
        if signum is not None:
            pass
            # smap = dict([(getattr(signal, x), x) for x in dir(signal)
            #              if x.startswith('SIG')])
            # print("signal {} received by process with PID {}".format(
            #     smap.get(signum, signum), os.getpid()))
        fun_wrapper()
        # Only return the original signal this process was hit with
        # in case fun returns with no errors, otherwise process will
        # return with sig 1.
        if signum is not None:
            if signum == signal.SIGINT:
                raise KeyboardInterrupt
            # XXX - should we do the same for SIGTERM / SystemExit?
            sys.exit(signum)

    if not callable(fun):
        raise TypeError("{!r} is not callable".format(fun))
    set([fun])  # raise exc if obj is not hash-able

    for sig in signals:
        # Register function for this signal and pop() the previously
        # registered one (if any). This can either be a callable,
        # SIG_IGN (ignore signal) or SIG_DFL (perform default action
        # for signal).
        old_handler = signal.signal(sig, signal_wrapper)
        if old_handler not in (signal.SIG_DFL, signal.SIG_IGN):
            # ...just for extra safety.
            if not callable(old_handler):
                continue
            # This is needed otherwise we'll get a KeyboardInterrupt
            # strace on interpreter exit, even if the process exited
            # with sig 0.
            if (sig == signal.SIGINT and
                    old_handler is signal.default_int_handler):
                continue
            # There was a function which was already registered for this
            # signal. Register it again so it will get executed (after our
            # new fun).
            if old_handler not in _registered_exit_funs:
                atexit.register(old_handler)
                _registered_exit_funs.add(old_handler)

    # This further registration will be executed in case of clean
    # interpreter exit (no signals received).
    if fun not in _registered_exit_funs or not signals:
        atexit.register(fun_wrapper)
        _registered_exit_funs.add(fun)

用法 ( Usage)

As a function:

作为功​​能:

def cleanup():
    print("cleanup")

register_exit_fun(cleanup)

As a decorator:

作为装饰:

@register_exit_fun
def cleanup():
    print("cleanup")

单元测试 ( Unit tests)

This recipe is currently provided as a gist with a full set of unittests. It works with Python 2 and 3.

目前,此食谱的要旨是提供全套的单元测试。 它适用于Python 2和3。

关于Windows的注意事项 ( Notes about Windows)

On Windows signals are only partially supported meaning a function which was previously registered via signal.signal() will be executed only on interpreter exit, but not if the process receives a signal. Apparently this is a limitation either of Windows or the signal module (most likely Windows).


Because of how different signal.signal() behaves on Windows, this code is UNIX only: http://bugs.python.org/issue26350
在Windows上,仅部分支持信号,这意味着先前通过signal.signal()注册的功能仅在解释器退出时执行,但如果进程接收到信号则不执行。 显然,这是Windows或 信号模块 (很可能是Windows)的限制。


由于Windows上signal.signal()的行为方式不同,因此该代码仅适用于UNIX: http ://bugs.python.org/issue26350

建议将stdlib包含在内 ( Proposal for stdlib inclusion)

The fact that atexit module does not handle signals and that signal.signal() overwrites previously registered handlers is unfortunate. It is also confusing because it is not immediately clear which one you are supposed to use (and it turns out you’re supposed to use both). Most of the times you have no idea (or don’t care) that you’re overwriting another exit function. As a user, I would just want to execute an exit function, no matter what, possibly without messing with whatever a module I’ve previously imported has done with signal.signal(). To me this suggests there could be space for something like “atexit.register_w_signals“.

不幸的是,atexit模块不处理信号,而那个signal.signal()会覆盖以前注册的处理程序。 这也很令人困惑,因为并不能立即弄清楚您应该使用哪一个(事实证明您应该同时使用两者)。 大多数时候,您不知道(或不在乎)您要覆盖另一个退出功能。 作为用户,无论如何,我只想执行一个退出函数,可能不会弄乱我先前导入的模块对signal.signal()所做的任何事情。 对我来说,这表明可能有空间容纳“ atexit.register_w_signals ”之类的东西。

外部讨论 ( External discussions)

翻译自: https://www.pybloggers.com/2016/02/how-to-always-execute-exit-functions-in-python/

python 退出执行

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值