Proposal: standard way of defining and executing "atexit" functions...
Python's sys module defines an exitfunc variable that is settable from Python scripts. At exit, that function will be called with no arguments. While this is a good start at supporting standard cleanup activities, it defines no protocol to be used by modules that wish to use sys.exitfunc, which leads to one of two extremes: 1, two modules wishing to define cleanup functions both clobber sys.exitfunc or 2, to avoid collisions they don't use the functionality provided. At an application level this is okay. Within a single application you can define an application-specific protocol to handle the situation. Unfortunately, this still leaves Python's core modules without a good way to register exit functions. I ran into this problem today. I would really like the rlcompleter module to read and write readline history files from Python. Reading a history file at module startup is no problem, but deciding where to write the history file is a problem. The logical place is at the time sys.exitfunc is executed. For my own applications I long ago wrote a very simple module (called exit.py, appended to this message) that defines two functions: * exit.register_exitfunc takes a function object and a set of optional arguments and appends them to a list. * exit._run_exitfuncs is bound to sys.exitfunc and executes the registered exit functions in the order they appear in the list. I propose exit.py as the starting point for a well-defined protocol for modules to register exit functions without collisions. -- Skip Montanaro, skip@mojam.com, http://www.mojam.com/, http://www.musi-cal.com/ On June 24th at 8AM, live your life for an hour as Ricky Byrdsong always lived his - run/walk in the Ricky Byrdsong Memorial 5K or just make a donation: https://www.SignmeupSports.com/Events/Index_Events.asp?EventID=1395 """ allow programmer to define multiple exit functions to be executed upon normal program termination. """ _exithandlers = [] def _run_exitfuncs(): while _exithandlers: func, targs, kargs = _exithandlers[0] apply(func, targs, kargs) _exithandlers.remove(_exithandlers[0]) def register_exitfunc(func, *targs, **kargs): """register a function to be executed upon normal program termination arguments are a function object, and zero or more arguments to pass to it. """ _exithandlers.append((func, targs, kargs)) import sys try: x = sys.exitfunc except AttributeError: sys.exitfunc = _run_exitfuncs del sys if __name__ == "__main__": def x1(): print "running x1" def x2(n): print "running x2(%s)" % `n` def x3(n, kwd=None): print "running x3(%s, kwd=%s)" % (`n`, `kwd`) register_exitfunc(x1) register_exitfunc(x2, 12) register_exitfunc(x3, 5, "bar") register_exitfunc(x3, "no kwd args")
+1 on the idea. I wonder if it makes sense to publish a mechanism to allow de-registering of callbacks, or if that's featuritis. Also, I'd change:
try: x = sys.exitfunc except AttributeError: sys.exitfunc = _run_exitfuncs del sys
to: try: register_exitfuncs(sys.exitfunc) finally: sys.exitfunc = _run_exitfuncs Or some such. --david
-----Original Message----- From: python-dev-admin@python.org [mailto:python-dev-admin@python.org]On Behalf Of Skip Montanaro Sent: Monday, June 19, 2000 1:18 PM To: python-dev@python.org Subject: [Python-Dev] Proposal: standard way of defining and executing "atexit" functions... Importance: Low
Python's sys module defines an exitfunc variable that is settable from Python scripts. At exit, that function will be called with no arguments. While this is a good start at supporting standard cleanup activities, it defines no protocol to be used by modules that wish to use sys.exitfunc, which leads to one of two extremes: 1, two modules wishing to define cleanup functions both clobber sys.exitfunc or 2, to avoid collisions they don't use the functionality provided.
At an application level this is okay. Within a single application you can define an application-specific protocol to handle the situation. Unfortunately, this still leaves Python's core modules without a good way to register exit functions.
I ran into this problem today. I would really like the rlcompleter module to read and write readline history files from Python. Reading a history file at module startup is no problem, but deciding where to write the history file is a problem. The logical place is at the time sys.exitfunc is executed.
For my own applications I long ago wrote a very simple module (called exit.py, appended to this message) that defines two functions:
* exit.register_exitfunc takes a function object and a set of optional arguments and appends them to a list.
* exit._run_exitfuncs is bound to sys.exitfunc and executes the registered exit functions in the order they appear in the list.
I propose exit.py as the starting point for a well-defined protocol for modules to register exit functions without collisions.
-- Skip Montanaro, skip@mojam.com, http://www.mojam.com/, http://www.musi-cal.com/ On June 24th at 8AM, live your life for an hour as Ricky Byrdsong always lived his - run/walk in the Ricky Byrdsong Memorial 5K or just make a donation: https://www.SignmeupSports.com/Events/Index_Events.asp?EventID=1395
""" allow programmer to define multiple exit functions to be executed upon normal program termination. """
_exithandlers = [] def _run_exitfuncs(): while _exithandlers: func, targs, kargs = _exithandlers[0] apply(func, targs, kargs) _exithandlers.remove(_exithandlers[0])
def register_exitfunc(func, *targs, **kargs): """register a function to be executed upon normal program termination
arguments are a function object, and zero or more arguments to pass to it. """ _exithandlers.append((func, targs, kargs))
import sys try: x = sys.exitfunc except AttributeError: sys.exitfunc = _run_exitfuncs del sys
if __name__ == "__main__": def x1(): print "running x1" def x2(n): print "running x2(%s)" % `n` def x3(n, kwd=None): print "running x3(%s, kwd=%s)" % (`n`, `kwd`)
register_exitfunc(x1) register_exitfunc(x2, 12) register_exitfunc(x3, 5, "bar") register_exitfunc(x3, "no kwd args")
_______________________________________________ Python-Dev mailing list Python-Dev@python.org http://www.python.org/mailman/listinfo/python-dev
David> +1 on the idea. I wonder if it makes sense to publish a David> mechanism to allow de-registering of callbacks, or if that's David> featuritis. While writing my message I thought, "I'll bet the first question asked will be about unregistering exit functions." I've been using the module I appended to my message unchanged for a couple years and never needed such functionality. I'm sure there are applications that could use it, but I think they'd be a small minority. Since the only defined interface is a single registration function (_run_exitfuncs is only used internally), I think you'd be free to add some unregister function protocol at a later time if it was deemed necessary. David> Also, I'd change: >> try: >> x = sys.exitfunc >> except AttributeError: >> sys.exitfunc = _run_exitfuncs >> del sys David> to: David> try: David> register_exitfuncs(sys.exitfunc) David> finally: David> sys.exitfunc = _run_exitfuncs David> Or some such. register_exitfunc is only meant to be called by clients of the module, not used internally. You're assuming that if something else was already bound to sys.exitfunc that it's not _run_exitfuncs. With your try/finally code try the following (mentally is fine): import exit def foo(): print 1 exit.register_exitfunc(foo) reload(exit) I think you'll see that exit._run_exitfuncs is on its own list of exit functions. That could be bad. The setup code: try: x = sys.exitfunc except AttributeError: sys.exitfunc = _run_exitfuncs is meant to avoid doing anything if any other module beat us to sys.exitfunc. Perhaps it should just be sys.exitfunc = _run_exitfuncs If someone fails to use the defined protocol, screw 'em... ;-) Skip
If someone fails to use the defined protocol, screw 'em... ;-)
That's unreasonable given that existing tools might include a library which might be upgraded to use the new mechanism while the old code used its own mechanism happily. For example, I've used my own protocols in the past which went something like... def register_exitfunc(func): old_exitfunc = getattr(sys, 'exitfunc', None) def wrapper(old_exitfunc=old_exitfunc, new_func=func): if old_exitfunc is not None: old_exitfunc() new_func() sys.exitfunc = wrapper or some such, creating a call chain instead of a sequence of calls, and it's a 'fine' protocol in that it worked fine in the absence of a standard. No need to break code if we can avoid it. The point of my pseudo-patch, if it wasn't clear, was that if a function is already in sys.exitfunc, then it should still be called on exit after the new exit module is imported. You're right that my patch didn't properly check that _run_exitfuncs might be that function. --david
>> If someone fails to use the defined protocol, screw 'em... ;-) David> That's unreasonable given that existing tools might include a David> library which might be upgraded to use the new mechanism while David> the old code used its own mechanism happily. Point taken. I'll look at extending the module to accommodate other sys.exitfunc callers. Skip
Hi Skip, Hi David, David Ascher:
If someone fails to use the defined protocol, screw 'em... ;-)
That's unreasonable given that existing tools might include a library which might be upgraded to use the new mechanism while the old code used its own mechanism happily. For example, I've used my own protocols in the past which went something like...
def register_exitfunc(func): old_exitfunc = getattr(sys, 'exitfunc', None) def wrapper(old_exitfunc=old_exitfunc, new_func=func): if old_exitfunc is not None: old_exitfunc() new_func() sys.exitfunc = wrapper
or some such, creating a call chain instead of a sequence of calls, and it's a 'fine' protocol in that it worked fine in the absence of a standard. No need to break code if we can avoid it.
Davids wrapper function implements a FIFO strategy for termination. I think this is not desired. Normally you want LIFO, because higher level services in an application are initialized later during startup. Those services usually depend on lower level services, which should still be available at termination time of the higher level services. I try to explain this by an example: Module A provides some kind of network connections. Module B uses (imports) Module A to access a remote resource administration server to provide access to remote devices or resources. During program termination Module B likes to release all resources reserved for (owned by) the program. It uses Module A to submit a cleanup request to the server. Module A however wants to close all open network connections. The following function (termination protocol) solves this by exchanging the sequence of calls within the wrapper: def register_exit_func(func, *args, **kw): import sys previous_func = sys.exit_func def wrapped_func(previous_func=previous_func, func=func, args=args, kw=kw): try: apply(func, args, kw) finally: if previous_func is not None: previous_func() sys.exit_func = wrapped_func In the past I've placed such a function into a common base module of my apps. But I think, Skip is right, that a termination protocol should be part of the standard library. But a separate module 'exit.py' seems to be overkill for single basic function. May be it should be put into the module 'os'? Handling of program termination is something, which considered as a generic operating system service. From the users point of view 'sys' would be a better place, but this would require rewriting in C: ugly. I also agree with Skip, that the possibility to unregister an exit_func is normally not needed. The same effect can be achieved by doing nothing in the exit_func. Regards, Peter -- Peter Funk, Oldenburger Str.86, D-27777 Ganderkesee, Germany, Fax:+49 4222950260 office: +49 421 20419-0 (ArtCom GmbH, Grazer Str.8, D-28359 Bremen)
[Skip's exit.py module + David's extension to support already registered exitfunc'tions] Looks like everyone agrees -- with a final word from Guido we'd only need some docs to go with it and then check Skip's module in as new standard module. -- Marc-Andre Lemburg ______________________________________________________________________ Business: http://www.lemburg.com/ Python Pages: http://www.lemburg.com/python/
MA> Looks like everyone agrees -- with a final word from Guido we'd only MA> need some docs to go with it and then check Skip's module in as new MA> standard module. I'll try to get David's enhancement added to my code and write some simple documentation today. Is "exit.py" acceptable with everyone as the name of the module? Skip
participants (5)
-
David Ascher
-
M.-A. Lemburg
-
Mark Hammond
-
pf@artcom-gmbh.de
-
Skip Montanaro