Table Of Contents

Previous topic

PyMOTW: commands

Next topic

PyMOTW: collections

This Page

PyMOTW: atexit

  • 模块: atexit
  • 目的: 当一程序关闭时, 注册一个需要被调用的函数.
  • Python版本: 2.1.3+

描述

atexit模块提供了一个简单的接口, 一般情况下, 用于注册当程序关闭时需要调用的函数. sys模块虽然也提供了类似功能的钩子, sys.exitfunc, 但是它只能注册一个函数. 而atexit注册表可以被多个模块和库同时使用.

示例

一个简单的例子, 它通过atexit.register()注册一个函数:

import atexit

def all_done():

    print 'all_done()'


print 'Registering'
atexit.register(all_done)
print 'Registered'

由于上面的程序实际上不做任何其他事情, 所以在程序关闭时立即调用了all_done():

$ python atexit_simple.py
Registering
Registered
all_done()

注册多个函数也是有可能的, 并且可以传递参数给它们. 这在安全地断开数据库连接, 或删除临时文件等情况下, 都是非常有用的. 因为可以将参数传递给注册函数, 所以我们甚至不需要保留一个单独需要清理东西的列表 – 我们只需要多次注册一个清理函数.

def my_cleanup(name):

    print 'my_cleanup(%s)' % name


atexit.register(my_cleanup, 'first')
atexit.register(my_cleanup, 'second')
atexit.register(my_cleanup, 'third')

注意: 这里函数调用的顺序是按照它们注册顺序的逆序的. 这就允许模块按照它们被导入顺序的逆序(由此来注册它们的atexit函数)来清理, 这样可以减少模块间的依赖关系.

$ python atexit_multiple.py
my_cleanup(third)
my_cleanup(second)
my_cleanup(first)

什么时候atexit函数不被调用?

由atexit注册的那些回调函数不会被调用的情况有以下几种:

  • 程序由于收到信号退出. ## 这个信号是??
  • 直接调用os._exit()
  • 在python解释器中, 检测到很严重的错误.

为了举例程序是通过信号被杀死的, 我们可以修改 subprocess summary 中的一个例子. 这里有2个文件需要被调用, 分别是父进程和子进程:

import os
import signal
import subprocess
import time

proc = subprocess.Popen('atexit_signal_child.py')
print 'PARENT: Pausing before sending signal...'
time.sleep(1)
print 'PARENT: Signaling %s' % proc.pid
os.kill(proc.pid, signal.SIGTERM)

子进程中设置atexit回调函数, 以证明它没有被调用.

import atexit
import time

def not_called():
    print 'CHILD: atexit handler should not have been called'

print 'CHILD: Registering atexit handler'
atexit.register(not_called)

print 'CHILD: Pausing to wait for signal'
time.sleep(5)

运行之后, 输出信息如下:

$ python atexit_signal_parent.py
CHILD: Registering atexit handler
CHILD: Pausing to wait for signal
PARENT: Pausing before sending signal...
PARENT: Signaling 2038

注意到子进程中没有调用not_called(), 所以就没有打印出相应的信息. 类似的, 如果一个程序绕过正常的退出路径的话, 它也不会执行atexit回调函数.

import atexit
import os

def not_called():

    print 'This should not be called'


print 'Registering'
atexit.register(not_called)
print 'Registered'

print 'Exiting...'
os._exit(0)

由于我们直接调用os._eixt()退出程序, 所以atexit的回调函数不会被调用.

$ python atexit_os_exit.py
Registering
Registered
Exiting...

如果我们使用sys.exit()的话, atexit的回调函数仍然会被执行.

import atexit
import sys

def all_done():

    print 'all_done()'


print 'Registering'
atexit.register(all_done)
print 'Registered'

print 'Exiting...'
sys.exit()
$ python atexit_sys_exit.py
Registering
Registered
Exiting...
all_done()

在Python解释器中模拟出一个严重错误来验证程序的退出也没有调用atexit回调函数,,,这个就留给读者了吧. :-)

在atexit回调函数中的异常

在atexit回调函数中引发异常后的回溯信息会被输出到控制台, 最后引发的异常会重新被抛出以作为程序的最终错误信息.

def exit_with_exception(message):
    raise RuntimeError(message)

atexit.register(exit_with_exception, 'Registered first')
atexit.register(exit_with_exception, 'Registered second')

Note

注册时的顺序决定了执行的顺序. 如果一个回调函数中的错误引入了另外一个错误(比他先注册但是比他后调用), 那么, 最终的信息可能对于用户来说不是最有用的. ## 这个例子, 程序首先在second上抛出异常, 但最终显示的是first异常信息, 这主要还是因为atexit回调函数不是按照注册顺序来执行的.

$ python atexit_exception.py
Error in atexit._run_exitfuncs:
Traceback (most recent call last):
 File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/atexit.py", line 24, in _run_exitfuncs
 func(*targs, **kargs)
 File "atexit_exception.py", line 36, in exit_with_exception
 raise RuntimeError(message)
RuntimeError: Registered second
Error in atexit._run_exitfuncs:
Traceback (most recent call last):
 File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/atexit.py", line 24, in _run_exitfuncs
 func(*targs, **kargs)
 File "atexit_exception.py", line 36, in exit_with_exception
 raise RuntimeError(message)
RuntimeError: Registered first
Error in sys.exitfunc:
Traceback (most recent call last):
 File "/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/atexit.py", line 24, in _run_exitfuncs
 func(*targs, **kargs)
 File "atexit_exception.py", line 36, in exit_with_exception
 raise RuntimeError(message)
RuntimeError: Registered first

一般情况下, 你可以设置在清理函数中安静的处理和记录所有异常, 这样在程序退出时, 就不会使输出信息显得很乱.