summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCollin Winter <collinw@gmail.com>2007-03-21 02:57:17 (GMT)
committerCollin Winter <collinw@gmail.com>2007-03-21 02:57:17 (GMT)
commit670e6921349dd408b6958a0c5d3b1486725f9beb (patch)
tree719d6b0e5348693a2d985dfabc5e9160f7f79108
parent450ee81b227b093eeb0b7a933fe6ddc6dc768d4a (diff)
downloadcpython-670e6921349dd408b6958a0c5d3b1486725f9beb.zip
cpython-670e6921349dd408b6958a0c5d3b1486725f9beb.tar.gz
cpython-670e6921349dd408b6958a0c5d3b1486725f9beb.tar.bz2
Patch #1680961: remove sys.exitfunc and replace it with a private C API. Also, reimplement atexit in C so it can take advantage of this private API.
-rw-r--r--Doc/lib/libatexit.tex36
-rw-r--r--Doc/lib/libsys.tex13
-rw-r--r--Include/pythonrun.h4
-rw-r--r--Lib/atexit.py65
-rw-r--r--Lib/test/test___all__.py1
-rw-r--r--Lib/test/test_atexit.py173
-rw-r--r--Misc/NEWS5
-rw-r--r--Modules/Setup.dist1
-rw-r--r--Modules/atexitmodule.c217
-rw-r--r--Python/import.c2
-rw-r--r--Python/pythonrun.c42
-rw-r--r--Python/sysmodule.c3
-rw-r--r--setup.py2
13 files changed, 360 insertions, 204 deletions
diff --git a/Doc/lib/libatexit.tex b/Doc/lib/libatexit.tex
index 9798b57..04f1d49 100644
--- a/Doc/lib/libatexit.tex
+++ b/Doc/lib/libatexit.tex
@@ -1,32 +1,21 @@
\section{\module{atexit} ---
Exit handlers}
-\declaremodule{standard}{atexit}
+\declaremodule{builtin}{atexit}
\moduleauthor{Skip Montanaro}{skip@mojam.com}
\sectionauthor{Skip Montanaro}{skip@mojam.com}
\modulesynopsis{Register and execute cleanup functions.}
\versionadded{2.0}
-The \module{atexit} module defines a single function to register
-cleanup functions. Functions thus registered are automatically
-executed upon normal interpreter termination.
-Note: the functions registered via this module are not called when the program is killed by a
-signal, when a Python fatal internal error is detected, or when
-\function{os._exit()} is called.
+The \module{atexit} module defines functions to register and
+unregister cleanup functions. Functions thus registered are
+automatically executed upon normal interpreter termination.
-This is an alternate interface to the functionality provided by the
-\code{sys.exitfunc} variable.
-\withsubitem{(in sys)}{\ttindex{exitfunc}}
-
-Note: This module is unlikely to work correctly when used with other code
-that sets \code{sys.exitfunc}. In particular, other core Python modules are
-free to use \module{atexit} without the programmer's knowledge. Authors who
-use \code{sys.exitfunc} should convert their code to use
-\module{atexit} instead. The simplest way to convert code that sets
-\code{sys.exitfunc} is to import \module{atexit} and register the function
-that had been bound to \code{sys.exitfunc}.
+Note: the functions registered via this module are not called when
+the program is killed by a signal, when a Python fatal internal
+error is detected, or when \function{os._exit()} is called.
\begin{funcdesc}{register}{func\optional{, *args\optional{, **kargs}}}
Register \var{func} as a function to be executed at termination. Any
@@ -47,7 +36,16 @@ chance to run the last exception to be raised is re-raised.
\versionchanged[This function now returns \var{func} which makes it
possible to use it as a decorator without binding the
- original name to \code{None}]{2.6}
+ original name to \code{None}]{2.6}
+\end{funcdesc}
+
+\begin{funcdesc}{unregister}{func}
+Remove a function \var{func} from the list of functions to be run at
+interpreter-shutdown. After calling \function{unregister()},
+\var{func} is guaranteed not to be called when the interpreter
+shuts down.
+
+\versionadded{3.0}
\end{funcdesc}
diff --git a/Doc/lib/libsys.tex b/Doc/lib/libsys.tex
index 7a8859b..ac161d9 100644
--- a/Doc/lib/libsys.tex
+++ b/Doc/lib/libsys.tex
@@ -218,19 +218,6 @@ It is always available.
program when an error occurs.
\end{funcdesc}
-\begin{datadesc}{exitfunc}
- This value is not actually defined by the module, but can be set by
- the user (or by a program) to specify a clean-up action at program
- exit. When set, it should be a parameterless function. This
- function will be called when the interpreter exits. Only one
- function may be installed in this way; to allow multiple functions
- which will be called at termination, use the \refmodule{atexit}
- module. \note{The exit function is not called when the program is
- killed by a signal, when a Python fatal internal error is detected,
- or when \code{os._exit()} is called.}
- \deprecated{2.4}{Use \refmodule{atexit} instead.}
-\end{datadesc}
-
\begin{funcdesc}{getcheckinterval}{}
Return the interpreter's ``check interval'';
see \function{setcheckinterval()}.
diff --git a/Include/pythonrun.h b/Include/pythonrun.h
index e0979b5..08278cf 100644
--- a/Include/pythonrun.h
+++ b/Include/pythonrun.h
@@ -69,6 +69,10 @@ PyAPI_FUNC(void) PyErr_Print(void);
PyAPI_FUNC(void) PyErr_PrintEx(int);
PyAPI_FUNC(void) PyErr_Display(PyObject *, PyObject *, PyObject *);
+/* Py_PyAtExit is for the atexit module, Py_AtExit is for low-level
+ * exit functions.
+ */
+PyAPI_FUNC(void) _Py_PyAtExit(void (*func)(void));
PyAPI_FUNC(int) Py_AtExit(void (*func)(void));
PyAPI_FUNC(void) Py_Exit(int);
diff --git a/Lib/atexit.py b/Lib/atexit.py
deleted file mode 100644
index 51a153e..0000000
--- a/Lib/atexit.py
+++ /dev/null
@@ -1,65 +0,0 @@
-"""
-atexit.py - allow programmer to define multiple exit functions to be executed
-upon normal program termination.
-
-One public function, register, is defined.
-"""
-
-__all__ = ["register"]
-
-import sys
-
-_exithandlers = []
-def _run_exitfuncs():
- """run any registered exit functions
-
- _exithandlers is traversed in reverse order so functions are executed
- last in, first out.
- """
-
- exc_info = None
- while _exithandlers:
- func, targs, kargs = _exithandlers.pop()
- try:
- func(*targs, **kargs)
- except SystemExit:
- exc_info = sys.exc_info()
- except:
- import traceback
- print("Error in atexit._run_exitfuncs:", file=sys.stderr)
- traceback.print_exc()
- exc_info = sys.exc_info()
-
- if exc_info is not None:
- raise exc_info[0], exc_info[1], exc_info[2]
-
-
-def register(func, *targs, **kargs):
- """register a function to be executed upon normal program termination
-
- func - function to be called at exit
- targs - optional arguments to pass to func
- kargs - optional keyword arguments to pass to func
-
- func is returned to facilitate usage as a decorator.
- """
- _exithandlers.append((func, targs, kargs))
- return func
-
-if hasattr(sys, "exitfunc"):
- # Assume it's another registered exit function - append it to our list
- register(sys.exitfunc)
-sys.exitfunc = _run_exitfuncs
-
-if __name__ == "__main__":
- def x1():
- print("running x1")
- def x2(n):
- print("running x2(%r)" % (n,))
- def x3(n, kwd=None):
- print("running x3(%r, kwd=%r)" % (n, kwd))
-
- register(x1)
- register(x2, 12)
- register(x3, 5, "bar")
- register(x3, "no kwd args")
diff --git a/Lib/test/test___all__.py b/Lib/test/test___all__.py
index d8e850a..bb1fd8d 100644
--- a/Lib/test/test___all__.py
+++ b/Lib/test/test___all__.py
@@ -48,7 +48,6 @@ class AllTest(unittest.TestCase):
self.check_all("StringIO")
self.check_all("UserString")
self.check_all("aifc")
- self.check_all("atexit")
self.check_all("audiodev")
self.check_all("base64")
self.check_all("bdb")
diff --git a/Lib/test/test_atexit.py b/Lib/test/test_atexit.py
index 9d1e003..56077e7 100644
--- a/Lib/test/test_atexit.py
+++ b/Lib/test/test_atexit.py
@@ -4,97 +4,112 @@ import StringIO
import atexit
from test import test_support
-class TestCase(unittest.TestCase):
- def test_args(self):
- # be sure args are handled properly
- s = StringIO.StringIO()
- sys.stdout = sys.stderr = s
- save_handlers = atexit._exithandlers
- atexit._exithandlers = []
- try:
- atexit.register(self.h1)
- atexit.register(self.h4)
- atexit.register(self.h4, 4, kw="abc")
- atexit._run_exitfuncs()
- finally:
- sys.stdout = sys.__stdout__
- sys.stderr = sys.__stderr__
- atexit._exithandlers = save_handlers
- self.assertEqual(s.getvalue(), "h4 (4,) {'kw': 'abc'}\nh4 () {}\nh1\n")
+### helpers
+def h1():
+ print("h1")
- def test_order(self):
- # be sure handlers are executed in reverse order
- s = StringIO.StringIO()
- sys.stdout = sys.stderr = s
- save_handlers = atexit._exithandlers
- atexit._exithandlers = []
- try:
- atexit.register(self.h1)
- atexit.register(self.h2)
- atexit.register(self.h3)
- atexit._run_exitfuncs()
- finally:
- sys.stdout = sys.__stdout__
- sys.stderr = sys.__stderr__
- atexit._exithandlers = save_handlers
- self.assertEqual(s.getvalue(), "h3\nh2\nh1\n")
+def h2():
+ print("h2")
- def test_sys_override(self):
- # be sure a preset sys.exitfunc is handled properly
- s = StringIO.StringIO()
- sys.stdout = sys.stderr = s
- save_handlers = atexit._exithandlers
- atexit._exithandlers = []
- exfunc = sys.exitfunc
- sys.exitfunc = self.h1
- reload(atexit)
- try:
- atexit.register(self.h2)
- atexit._run_exitfuncs()
- finally:
- sys.stdout = sys.__stdout__
- sys.stderr = sys.__stderr__
- atexit._exithandlers = save_handlers
- sys.exitfunc = exfunc
- self.assertEqual(s.getvalue(), "h2\nh1\n")
+def h3():
+ print("h3")
- def test_raise(self):
- # be sure raises are handled properly
- s = StringIO.StringIO()
- sys.stdout = sys.stderr = s
- save_handlers = atexit._exithandlers
- atexit._exithandlers = []
- try:
- atexit.register(self.raise1)
- atexit.register(self.raise2)
- self.assertRaises(TypeError, atexit._run_exitfuncs)
- finally:
- sys.stdout = sys.__stdout__
- sys.stderr = sys.__stderr__
- atexit._exithandlers = save_handlers
+def h4(*args, **kwargs):
+ print("h4", args, kwargs)
+
+def raise1():
+ raise TypeError
- ### helpers
- def h1(self):
- print("h1")
+def raise2():
+ raise SystemError
- def h2(self):
- print("h2")
+class TestCase(unittest.TestCase):
+ def setUp(self):
+ self.stream = StringIO.StringIO()
+ sys.stdout = sys.stderr = self.stream
+ atexit._clear()
+
+ def tearDown(self):
+ sys.stdout = sys.__stdout__
+ sys.stderr = sys.__stderr__
+ atexit._clear()
- def h3(self):
- print("h3")
+ def test_args(self):
+ # be sure args are handled properly
+ atexit.register(h1)
+ atexit.register(h4)
+ atexit.register(h4, 4, kw="abc")
+ atexit._run_exitfuncs()
- def h4(self, *args, **kwargs):
- print("h4", args, kwargs)
+ self.assertEqual(self.stream.getvalue(),
+ "h4 (4,) {'kw': 'abc'}\nh4 () {}\nh1\n")
- def raise1(self):
- raise TypeError
+ def test_order(self):
+ # be sure handlers are executed in reverse order
+ atexit.register(h1)
+ atexit.register(h2)
+ atexit.register(h3)
+ atexit._run_exitfuncs()
+
+ self.assertEqual(self.stream.getvalue(), "h3\nh2\nh1\n")
- def raise2(self):
- raise SystemError
+ def test_raise(self):
+ # be sure raises are handled properly
+ atexit.register(raise1)
+ atexit.register(raise2)
+
+ self.assertRaises(TypeError, atexit._run_exitfuncs)
+
+ def test_stress(self):
+ a = [0]
+ def inc():
+ a[0] += 1
+
+ for i in range(128):
+ atexit.register(inc)
+ atexit._run_exitfuncs()
+
+ self.assertEqual(a[0], 128)
+
+ def test_clear(self):
+ a = [0]
+ def inc():
+ a[0] += 1
+
+ atexit.register(inc)
+ atexit._clear()
+ atexit._run_exitfuncs()
+
+ self.assertEqual(a[0], 0)
+
+ def test_unregister(self):
+ a = [0]
+ def inc():
+ a[0] += 1
+ def dec():
+ a[0] -= 1
+
+ for i in range(4):
+ atexit.register(inc)
+ atexit.register(dec)
+ atexit.unregister(inc)
+ atexit._run_exitfuncs()
+
+ self.assertEqual(a[0], -1)
+
+ def test_bound_methods(self):
+ l = []
+ atexit.register(l.append, 5)
+ atexit._run_exitfuncs()
+ self.assertEqual(l, [5])
+
+ atexit.unregister(l.append)
+ atexit._run_exitfuncs()
+ self.assertEqual(l, [5])
+
def test_main():
test_support.run_unittest(TestCase)
-
if __name__ == "__main__":
test_main()
diff --git a/Misc/NEWS b/Misc/NEWS
index adaf180..002a26c 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -28,6 +28,9 @@ TO DO
Core and Builtins
-----------------
+- Patch #1680961: sys.exitfunc has been removed and replaced with a private
+ C-level API.
+
- PEP 3115: new metaclasses: the metaclass is now specified as a
keyword arg in the class statement, which can now use the full syntax of
a parameter list. Also, the metaclass can implement a __prepare__ function
@@ -156,6 +159,8 @@ Extension Modules
Library
-------
+- Patch #1680961: atexit has been reimplemented in C.
+
- Removed all traces of the sets module.
Build
diff --git a/Modules/Setup.dist b/Modules/Setup.dist
index 46f4253..173dfcc 100644
--- a/Modules/Setup.dist
+++ b/Modules/Setup.dist
@@ -176,6 +176,7 @@ GLHACK=-Dclear=__GLclear
#collections collectionsmodule.c # Container types
#itertools itertoolsmodule.c # Functions creating iterators for efficient looping
#strop stropmodule.c # String manipulations
+#atexit atexitmodule.c # Register functions to be run at interpreter-shutdown
#unicodedata unicodedata.c # static Unicode character database
diff --git a/Modules/atexitmodule.c b/Modules/atexitmodule.c
new file mode 100644
index 0000000..b8a8bf6
--- /dev/null
+++ b/Modules/atexitmodule.c
@@ -0,0 +1,217 @@
+/*
+ * atexit - allow programmer to define multiple exit functions to be executed
+ * upon normal program termination.
+ *
+ * Translated from atexit.py by Collin Winter.
+ + Copyright 2007 Python Software Foundation.
+ */
+
+#include "Python.h"
+
+/* ===================================================================== */
+/* Callback machinery. */
+
+typedef struct {
+ PyObject *func;
+ PyObject *args;
+ PyObject *kwargs;
+} atexit_callback;
+
+atexit_callback **atexit_callbacks;
+int ncallbacks = 0;
+int callback_len = 32;
+
+/* Installed into pythonrun.c's atexit mechanism */
+
+void
+atexit_callfuncs(void)
+{
+ PyObject *exc_type = NULL, *exc_value, *exc_tb, *r;
+ atexit_callback *cb;
+ int i;
+
+ if (ncallbacks == 0)
+ return;
+
+ for(i = ncallbacks - 1; i >= 0; i--)
+ {
+ cb = atexit_callbacks[i];
+ if (cb == NULL)
+ continue;
+
+ r = PyObject_Call(cb->func, cb->args, cb->kwargs);
+ Py_XDECREF(r);
+ if (r == NULL) {
+ if (exc_type) {
+ Py_DECREF(exc_type);
+ Py_DECREF(exc_value);
+ Py_DECREF(exc_tb);
+ }
+ PyErr_Fetch(&exc_type, &exc_value, &exc_tb);
+ if (!PyErr_ExceptionMatches(PyExc_SystemExit)) {
+ PySys_WriteStderr("Error in atexit._run_exitfuncs:\n");
+ PyErr_Display(exc_type, exc_value, exc_tb);
+ }
+ }
+ }
+
+ if (exc_type)
+ PyErr_Restore(exc_type, exc_value, exc_tb);
+}
+
+void
+atexit_delete_cb(int i)
+{
+ atexit_callback *cb = atexit_callbacks[i];
+ atexit_callbacks[i] = NULL;
+ Py_DECREF(cb->func);
+ Py_DECREF(cb->args);
+ Py_XDECREF(cb->kwargs);
+ PyMem_Free(cb);
+}
+
+/* ===================================================================== */
+/* Module methods. */
+
+PyDoc_STRVAR(atexit_register__doc__,
+"register(func, *args, **kwargs) -> func\n\
+\n\
+Register a function to be executed upon normal program termination\n\
+\n\
+ func - function to be called at exit\n\
+ args - optional arguments to pass to func\n\
+ kwargs - optional keyword arguments to pass to func\n\
+\n\
+ func is returned to facilitate usage as a decorator.");
+
+static PyObject *
+atexit_register(PyObject *self, PyObject *args, PyObject *kwargs)
+{
+ atexit_callback *new_callback;
+ PyObject *func = NULL;
+
+ if (ncallbacks >= callback_len) {
+ callback_len += 16;
+ atexit_callbacks = PyMem_Realloc(atexit_callbacks,
+ sizeof(atexit_callback*) * callback_len);
+
+ }
+
+ if (PyTuple_GET_SIZE(args) == 0) {
+ PyErr_SetString(PyExc_TypeError,
+ "register() takes at least 1 argument (0 given)");
+ return NULL;
+ }
+
+ func = PyTuple_GET_ITEM(args, 0);
+ if (!PyCallable_Check(func)) {
+ PyErr_SetString(PyExc_TypeError,
+ "the first argument must be callable");
+ return NULL;
+ }
+
+ new_callback = PyMem_Malloc(sizeof(atexit_callback));
+ if (new_callback == NULL)
+ return PyErr_NoMemory();
+
+ new_callback->args = PyTuple_GetSlice(args, 1, PyTuple_GET_SIZE(args));
+ if (new_callback->args == NULL) {
+ PyMem_Free(new_callback);
+ return NULL;
+ }
+ new_callback->func = func;
+ new_callback->kwargs = kwargs;
+ Py_INCREF(func);
+ Py_XINCREF(kwargs);
+
+ atexit_callbacks[ncallbacks++] = new_callback;
+
+ Py_INCREF(func);
+ return func;
+}
+
+static PyObject *
+atexit_run_exitfuncs(PyObject *self)
+{
+ atexit_callfuncs();
+ if (PyErr_Occurred())
+ return NULL;
+ Py_RETURN_NONE;
+}
+
+static PyObject *
+atexit_clear(PyObject *self)
+{
+ atexit_callback *cb;
+ int i;
+
+ for(i = 0; i < ncallbacks; i++)
+ {
+ cb = atexit_callbacks[i];
+ if (cb == NULL)
+ continue;
+
+ atexit_delete_cb(i);
+ }
+ ncallbacks = 0;
+ Py_RETURN_NONE;
+}
+
+static PyObject *
+atexit_unregister(PyObject *self, PyObject *func)
+{
+ atexit_callback *cb;
+ int i, eq;
+
+ for(i = 0; i < ncallbacks; i++)
+ {
+ cb = atexit_callbacks[i];
+ if (cb == NULL)
+ continue;
+
+ eq = PyObject_RichCompareBool(cb->func, func, Py_EQ);
+ if (eq < 0)
+ return NULL;
+ if (eq)
+ atexit_delete_cb(i);
+ }
+ Py_RETURN_NONE;
+}
+
+static PyMethodDef atexit_methods[] = {
+ {"register", (PyCFunction) atexit_register, METH_VARARGS|METH_KEYWORDS,
+ atexit_register__doc__},
+ {"_clear", (PyCFunction) atexit_clear, METH_NOARGS,
+ NULL},
+ {"unregister", (PyCFunction) atexit_unregister, METH_O,
+ NULL},
+ {"_run_exitfuncs", (PyCFunction) atexit_run_exitfuncs, METH_NOARGS,
+ NULL},
+ {NULL, NULL} /* sentinel */
+};
+
+/* ===================================================================== */
+/* Initialization function. */
+
+PyDoc_STRVAR(atexit__doc__,
+"atexit.py - allow programmer to define multiple exit functions to be executed\
+upon normal program termination.\n\
+\n\
+One public function, register, is defined.\n\
+");
+
+PyMODINIT_FUNC
+initatexit(void)
+{
+ PyObject *m;
+
+ atexit_callbacks = PyMem_New(atexit_callback*, callback_len);
+ if (atexit_callbacks == NULL)
+ return;
+
+ m = Py_InitModule3("atexit", atexit_methods, atexit__doc__);
+ if (m == NULL)
+ return;
+
+ _Py_PyAtExit(atexit_callfuncs);
+}
diff --git a/Python/import.c b/Python/import.c
index 33953c7..6d65703 100644
--- a/Python/import.c
+++ b/Python/import.c
@@ -361,7 +361,7 @@ PyImport_GetModuleDict(void)
/* List of names to clear in sys */
static char* sys_deletes[] = {
- "path", "argv", "ps1", "ps2", "exitfunc",
+ "path", "argv", "ps1", "ps2",
"exc_type", "exc_value", "exc_traceback",
"last_type", "last_value", "last_traceback",
"path_hooks", "path_importer_cache", "meta_path",
diff --git a/Python/pythonrun.c b/Python/pythonrun.c
index d60ca9e..31c8329 100644
--- a/Python/pythonrun.c
+++ b/Python/pythonrun.c
@@ -56,7 +56,7 @@ static PyObject *run_pyc_file(FILE *, const char *, PyObject *, PyObject *,
PyCompilerFlags *);
static void err_input(perrdetail *);
static void initsigs(void);
-static void call_sys_exitfunc(void);
+static void call_py_exitfuncs(void);
static void call_ll_exitfuncs(void);
extern void _PyUnicode_Init(void);
extern void _PyUnicode_Fini(void);
@@ -355,7 +355,7 @@ Py_Finalize(void)
* threads created thru it, so this also protects pending imports in
* the threads created via Threading.
*/
- call_sys_exitfunc();
+ call_py_exitfuncs();
initialized = 0;
/* Get current thread state and interpreter pointer */
@@ -1557,6 +1557,23 @@ Py_FatalError(const char *msg)
#include "pythread.h"
#endif
+static void (*pyexitfunc)(void) = NULL;
+/* For the atexit module. */
+void _Py_PyAtExit(void (*func)(void))
+{
+ pyexitfunc = func;
+}
+
+static void
+call_py_exitfuncs(void)
+{
+ if (pyexitfunc == NULL)
+ return;
+
+ (*pyexitfunc)();
+ PyErr_Clear();
+}
+
#define NEXITFUNCS 32
static void (*exitfuncs[NEXITFUNCS])(void);
static int nexitfuncs = 0;
@@ -1570,27 +1587,6 @@ int Py_AtExit(void (*func)(void))
}
static void
-call_sys_exitfunc(void)
-{
- PyObject *exitfunc = PySys_GetObject("exitfunc");
-
- if (exitfunc) {
- PyObject *res;
- Py_INCREF(exitfunc);
- PySys_SetObject("exitfunc", (PyObject *)NULL);
- res = PyEval_CallObject(exitfunc, (PyObject *)NULL);
- if (res == NULL) {
- if (!PyErr_ExceptionMatches(PyExc_SystemExit)) {
- PySys_WriteStderr("Error in sys.exitfunc:\n");
- }
- PyErr_Print();
- }
- Py_DECREF(exitfunc);
- }
-
-}
-
-static void
call_ll_exitfuncs(void)
{
while (nexitfuncs > 0)
diff --git a/Python/sysmodule.c b/Python/sysmodule.c
index c7d85933..d3c90bf 100644
--- a/Python/sysmodule.c
+++ b/Python/sysmodule.c
@@ -897,9 +897,6 @@ excepthook -- called to handle any uncaught exception other than SystemExit\n\
To customize printing in an interactive session or to install a custom\n\
top-level exception handler, assign other functions to replace these.\n\
\n\
-exitfunc -- if sys.exitfunc exists, this routine is called when Python exits\n\
- Assigning to sys.exitfunc is deprecated; use the atexit module instead.\n\
-\n\
stdin -- standard input file object; used by raw_input() and input()\n\
stdout -- standard output file object; used by print()\n\
stderr -- standard error object; used for error messages\n\
diff --git a/setup.py b/setup.py
index 573fba2..f1b7460 100644
--- a/setup.py
+++ b/setup.py
@@ -379,6 +379,8 @@ class PyBuildExt(build_ext):
exts.append( Extension('operator', ['operator.c']) )
# _functools
exts.append( Extension("_functools", ["_functoolsmodule.c"]) )
+ # atexit
+ exts.append( Extension("atexit", ["atexitmodule.c"]) )
# Python C API test module
exts.append( Extension('_testcapi', ['_testcapimodule.c']) )
# profilers (_lsprof is for cProfile.py)