]> granicus.if.org Git - python/commitdiff
Patch #1680961: remove sys.exitfunc and replace it with a private C API. Also, reimpl...
authorCollin Winter <collinw@gmail.com>
Wed, 21 Mar 2007 02:57:17 +0000 (02:57 +0000)
committerCollin Winter <collinw@gmail.com>
Wed, 21 Mar 2007 02:57:17 +0000 (02:57 +0000)
13 files changed:
Doc/lib/libatexit.tex
Doc/lib/libsys.tex
Include/pythonrun.h
Lib/atexit.py [deleted file]
Lib/test/test___all__.py
Lib/test/test_atexit.py
Misc/NEWS
Modules/Setup.dist
Modules/atexitmodule.c [new file with mode: 0644]
Python/import.c
Python/pythonrun.c
Python/sysmodule.c
setup.py

index 9798b5750744b1e80abd87b13b899b9f6d82b6ab..04f1d492df1584c79a6345bab0e2e3578dccfa40 100644 (file)
@@ -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}
 
 
index 7a8859bfb466077c6b26431f2fcbb311950b064e..ac161d930e04ea3d9e66ce5a27da98706f363b49 100644 (file)
@@ -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()}.
index e0979b5894b07b5e975bbd8db7e70ae78de14422..08278cf8fb55380d28101fbf9cf16d6886b12435 100644 (file)
@@ -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 (file)
index 51a153e..0000000
+++ /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")
index d8e850a1bf979148ad9f591b07f01db34809aed9..bb1fd8dd70f7198ae6988292af43d46c075b017d 100644 (file)
@@ -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")
index 9d1e003bcd0e72b71565962810cedd3359fbdf89..56077e76b9ed23a0c6244777316ba49dce4d6e83 100644 (file)
@@ -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()
index adaf180e6d47640fc2b4321ff90fd4a613e1add9..002a26c80c2e29f1a7e91123471f2c518daef58c 100644 (file)
--- 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
index 46f4253e1c6de31bc5d0c4f8453bcbea0c249ae6..173dfcc23485683f71369601b3ff49ea5a345d4e 100644 (file)
@@ -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 (file)
index 0000000..b8a8bf6
--- /dev/null
@@ -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);
+}
index 33953c7e547993a7e4fd38d17e4aad8322f40819..6d65703184b2606fd30e505f51c5d8206c4fe237 100644 (file)
@@ -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",
index d60ca9e79b48a4181d7a608adcb45bdbc676d31a..31c83299f25b9215230b9ed39aae27d6ce427878 100644 (file)
@@ -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;
@@ -1569,27 +1586,6 @@ int Py_AtExit(void (*func)(void))
        return 0;
 }
 
-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)
 {
index c7d85933963a36224d3c5910b175e86cf31c9a91..d3c90bfcac734e7641b87f0e93217e4d2029118a 100644 (file)
@@ -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\
index 573fba2f45873c78c3211315fb84d2eed4fa0611..f1b7460f79208f3f455acab7d4e35ce31181d689 100644 (file)
--- 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)