# HG changeset patch # Parent 68e2690a471d0edc5dfa15585ad809fc52906902 diff -r 68e2690a471d Doc/library/weakref.rst --- a/Doc/library/weakref.rst Sat Jul 28 17:45:28 2012 +0100 +++ b/Doc/library/weakref.rst Wed Aug 01 16:40:41 2012 +0100 @@ -48,10 +48,15 @@ but keeps weak references to its elements, just like a :class:`WeakKeyDictionary` does. -Most programs should find that using one of these weak container types is all -they need -- it's not usually necessary to create your own weak references -directly. The low-level machinery used by the weak dictionary implementations -is exposed by the :mod:`weakref` module for the benefit of advanced uses. +:class:`finalize` provides a high level means of registering a cleanup +function to be called when an object is garbage collected. This is +simpler to use than setting up a callback function on a raw weak +reference. + +Most programs should find that using one of these weak container types +or :class:`finalize` is all they need -- it's not usually necessary to +create your own weak references directly. The low-level machinery is +exposed by the :mod:`weakref` module for the benefit of advanced uses. .. note:: @@ -195,6 +200,61 @@ discarded when no strong reference to it exists any more. +.. class:: finalize(obj, func, *args, **kwds) + + Returns a finalizer object which will be called when *obj* is + garbage collected. The first time the finalizer is called it + evaluates ``func(*arg, **kwds)`` and returns the result. After + this the finalizer is dead, and calling it just returns + :const:`None`. + + It is important to ensure that *func*, *args* and *kwds* do not own + any references to *obj*, either directly or indirectly. Otherwise + *obj* will never be garbage collected. + + Exceptions raised by finalizer callbacks during garbage collection + will be shown on the standard error output, but cannot be + propagated. They are handled in the same way as exceptions raised + from an object's :meth:`__del__` method or a weak reference's + callback, except for the fact that a traceback is shown to aid + debugging. + + A finalizer will never be invoke its callback during the later part + of the interpreter shutdown when module globals are liable to have + been replaced by :const:`None`. + + :class:`finalize` is a subclassable type rather than a factory + function. + + .. method:: __call__() + + If the finalizer is dead return :const:`None`. Otherwise, mark + it as dead and return the result of calling ``func(*args, + **kwds)``. + + .. method:: pop() + + If the finalizer is dead return :const:`None`. Otherwise, mark + it as dead and return the tuple ``(wr, func, args, kwds)`` + where *wr* is a weak reference to *obj*. + + .. method:: get() + + If the finalizer is dead return :const:`None`. Otherwise, + return the tuple ``(wr, func, args, kwds)`` where *wr* is a weak + reference to *obj*. + + .. attribute:: atexit + + A boolean attribute indicating that, if the finalizer is still + alive when program exits, it should be called then. By default + this is :const:`False`. + + When the program does exit, it runs the remaining live + finalizers for which the :attr:`.atexit` attribute is true. + These are called in reverse order of creation. + + .. data:: ReferenceType The type object for weak references objects. @@ -329,3 +389,118 @@ def id2obj(oid): return _id2obj_dict[oid] + +.. _finalize-examples: + +Finalizer Objects +----------------- + +Often one simply uses :class:`finalize` to register a callback without +bothering to keep the created finalizer object. For instance + + >>> import weakref + >>> class Object: pass + ... + >>> kenny = Object() + >>> weakref.finalize(kenny, print, "you killed kenny!") #doctest:+ELLIPSIS + + >>> del kenny + you killed kenny! + +While a finalizer is still alive you can access the information +registered to it by using the :meth:`~finalize.get` method: + + >>> def callback(x, y, z): + ... print("CALLBACK") + ... return x + y + z + ... + >>> obj = Object() + >>> f = weakref.finalize(obj, callback, 1, 2, z=3) + >>> f.get() #doctest:+ELLIPSIS + (, , (1, 2), {'z': 3}) + >>> wr, func, args, kwds = _ + >>> assert wr() is obj + >>> assert func is callback + +Once a finalizer has been called, whether explicitly or automatically +during garbage collection, it is dead. A dead finalizer's public +methods (:meth:`~finalize.__call__`, :meth:`~finalize.get` and +:meth:`~finalize.pop`) all just return :const:`None`, and the +finalizer does nothing if the target object is garbage collected: + + >>> f() + CALLBACK + 6 + >>> f(), f.get(), f.pop() + (None, None, None) + >>> del obj + +To stop a finalizer's callback from being invoked when the target +object is garbage collected, you can call the :meth:`~finalize.pop` +method. This makes the finalizer dead and returns the same +information as the :meth:`~finalize.get` method: + + >>> obj = Object() + >>> f = weakref.finalize(obj, callback, 1, 2, z=3) + >>> wr, func, args, kwds = f.pop() + >>> f(), f.get(), f.pop() + (None, None, None) + >>> del obj + >>> func(*args, **kwds) # explicitly call the popped callback + CALLBACK + 6 + +Sometimes you may want a finalizer to be called before the program +exits even if the target object has not been garbage collected. In +this case you can set the :attr:`~finalize.atexit` attribute to +:const:`True`. + + >>> kenny = Object() + >>> f = weakref.finalize(kenny, print, "kenny must die!") + >>> f.atexit = True + >>> exit() #doctest:+SKIP + kenny must die! + +Those finalizers which must be be called at the program's exit are +called very early during interpreter shutdown, and after this no more +finalizer callbacks will be called. This avoids problems with +invoking callbacks later on during interpreter shutdown when module +globals have been replaced by :const:`None`. (Weak reference +callbacks and :meth:`__del__` methods often run into trouble if they +are triggered after module teardown has begun.) + + +Using a Finalizer in a Class +---------------------------- + +Suppose we want to create a subclass of :class:`int` which represents +file descriptors, and which automatically handles resource cleanup +when instances are garbage collected. One can achieve this by doing +:: + + class Fd(int): + def __init__(self, fd): + weakref.finalize(self, os.close, fd) + +Now suppose we also want to be able to explictly close the descriptor, +detach the descriptor, and check whether it is still open. (Compare +the `PyHandle +`_ +class used by `PyWin32 `_.) +This can be done as follows:: + + class Fd(int): + def __init__(self, fd): + self._close = weakref.finalize(self, os.close, fd) + + def close(self): + self._close() + + def detach(self): + if self._close.pop() is None: + raise ValueError("already closed or detached") + return int(self) + + @property + def closed(self): + return self._close.get() is None diff -r 68e2690a471d Lib/test/test_weakref.py --- a/Lib/test/test_weakref.py Sat Jul 28 17:45:28 2012 +0100 +++ b/Lib/test/test_weakref.py Wed Aug 01 16:40:41 2012 +0100 @@ -6,12 +6,17 @@ import operator import contextlib import copy +import subprocess from test import support # Used in ReferencesTestCase.test_ref_created_during_del() . ref_from_del = None +# Used by FinalizeTestCase as a global that may be replaced by None +# when the interpreter shuts down. +_global_var = 'foobar' + class C: def method(self): pass @@ -1304,6 +1309,167 @@ def _reference(self): return self.__ref.copy() + +class FinalizeTestCase(unittest.TestCase): + + class A: + pass + + def _collect_if_necessary(self): + # we create no ref-cycles so in CPython no gc should be needed + if sys.implementation.name != 'cpython': + support.gc_collect() + + def test_finalize(self): + def add(x,y,z): + res.append(x + y + z) + return x + y + z + + a = self.A() + + res = [] + f = weakref.finalize(a, add, 67, 43, z=89) + wr, func, args, kwds = f.get() + self.assertEqual([wr(), func, args, kwds], [a, add, (67,43), {'z':89}]) + self.assertEqual(f(), 199) # deregisters callback + self.assertEqual(f(), None) + self.assertEqual(f(), None) + self.assertEqual(f.get(), None) + self.assertEqual(f.pop(), None) + self.assertEqual(res, [199]) + + res = [] + f = weakref.finalize(a, add, 67, 43, z=89) + wr, func, args, kwds = f.pop() # deregisters callback + self.assertEqual([wr(), func, args, kwds], [a, add, (67,43), {'z':89}]) + self.assertEqual(f(), None) + self.assertEqual(f(), None) + self.assertEqual(f.get(), None) + self.assertEqual(f.pop(), None) + self.assertEqual(res, []) + + res = [] + f = weakref.finalize(a, add, 67, 43, z=89) + del a # deregisters callback + self._collect_if_necessary() + self.assertEqual(f(), None) + self.assertEqual(f(), None) + self.assertEqual(f.get(), None) + self.assertEqual(f.pop(), None) + self.assertEqual(res, [199]) + + def test_order(self): + a = self.A() + res = [] + + f1 = weakref.finalize(a, res.append, 'f1') + f2 = weakref.finalize(a, res.append, 'f2') + f3 = weakref.finalize(a, res.append, 'f3') + f4 = weakref.finalize(a, res.append, 'f4') + f5 = weakref.finalize(a, res.append, 'f5') + + # make sure finalizers can keep themselves alive + del f1, f4 + + self.assertTrue(f2.get()) + self.assertTrue(f3.get()) + self.assertTrue(f5.get()) + + self.assertTrue(f5.pop()) + self.assertFalse(f5.get()) + + f5() # nothing because previously unregistered + res.append('A') + f3() # => res.append('f3') + self.assertFalse(f3.get()) + res.append('B') + f3() # nothing because previously called + res.append('C') + del a + self._collect_if_necessary() + # => res.append('f4') + # => res.append('f2') + # => res.append('f1') + self.assertFalse(f2.get()) + res.append('D') + f2() # nothing because previously called by gc + + expected = ['A', 'f3', 'B', 'C', 'f4', 'f2', 'f1', 'D'] + self.assertEqual(res, expected) + + def test_all_freed(self): + # we want a weakrefable subclass of weakref.finalize + class myfinalize(weakref.finalize): + pass + + a = self.A() + res = [] + def callback(): + res.append(123) + f = myfinalize(a, callback) + + wr_callback = weakref.ref(callback) + wr_f = weakref.ref(f) + del callback, f + + self.assertIsNotNone(wr_callback()) + self.assertIsNotNone(wr_f()) + + del a + self._collect_if_necessary() + + self.assertIsNone(wr_callback()) + self.assertIsNone(wr_f()) + self.assertEqual(res, [123]) + + def test_traceback(self): + def f(): + g() + + def g(): + 1/0 + + with support.captured_stderr() as stderr: + a = self.A() + weakref.finalize(a, f) + del a + self._collect_if_necessary() + + def process(tb): + lines = [line.strip() for line in tb.split('\n')] + lines[1] = '...' + lines[1][-6:] + lines[3] = '...' + lines[3][-6:] + return lines + + expected = '''\ + Ignored exception in finalizer - Traceback (most recent call last): + File ..., line ..., in f + g() + File ..., line ..., in g + 1/0 + ZeroDivisionError: division by zero + ''' + self.assertEqual(process(stderr.getvalue()), process(expected)) + + @classmethod + def run_in_child(cls): + # cls should stay alive till atexit callbacks run + f1 = weakref.finalize(cls, print, 'f1', _global_var) + f2 = weakref.finalize(cls, print, 'f2', _global_var) + f3 = weakref.finalize(cls, print, 'f3', _global_var) + f1.atexit = True + assert f2.atexit == False + f3.atexit = True + + def test_atexit(self): + prog = ('from test.test_weakref import FinalizeTestCase;'+ + 'FinalizeTestCase.run_in_child()') + output = subprocess.check_output([sys.executable, '-c', prog]) + output = output.decode('ascii') + output = output.strip().split('\n') + self.assertEqual(output, ['f3 foobar', 'f1 foobar']) + + libreftest = """ Doctest for examples in the library reference: weakref.rst >>> import weakref @@ -1385,6 +1551,55 @@ ... print('WeakValueDictionary error') OK + +>>> import weakref +>>> class Object: pass +... +>>> kenny = Object() +>>> weakref.finalize(kenny, print, "you killed kenny!") #doctest:+ELLIPSIS + +>>> del kenny +you killed kenny! + + +>>> def callback(x, y, z): +... print("CALLBACK") +... return x + y + z +... +>>> obj = Object() +>>> f = weakref.finalize(obj, callback, 1, 2, z=3) +>>> f.get() #doctest:+ELLIPSIS +(, , (1, 2), {'z': 3}) +>>> wr, func, args, kwds = _ +>>> assert wr() is obj +>>> assert func is callback + + +>>> f() +CALLBACK +6 +>>> f(), f.get(), f.pop() +(None, None, None) +>>> del obj + + +>>> obj = Object() +>>> f = weakref.finalize(obj, callback, 1, 2, z=3) +>>> wr, func, args, kwds = f.pop() +>>> f(), f.get(), f.pop() +(None, None, None) +>>> del obj +>>> func(*args, **kwds) # explicitly call the popped callback +CALLBACK +6 + + +>>> kenny = Object() +>>> f = weakref.finalize(kenny, print, "kenny must die!") +>>> f.atexit = True +>>> exit() #doctest:+SKIP +kenny must die! + """ __test__ = {'libreftest' : libreftest} @@ -1396,6 +1611,7 @@ WeakValueDictionaryTestCase, WeakKeyDictionaryTestCase, SubclassableWeakrefTestCase, + FinalizeTestCase, ) support.run_doctest(sys.modules[__name__]) diff -r 68e2690a471d Lib/weakref.py --- a/Lib/weakref.py Sat Jul 28 17:45:28 2012 +0100 +++ b/Lib/weakref.py Wed Aug 01 16:40:41 2012 +0100 @@ -21,13 +21,15 @@ from _weakrefset import WeakSet, _IterationGuard import collections # Import after _weakref to avoid circular import. +import sys +import atexit ProxyTypes = (ProxyType, CallableProxyType) __all__ = ["ref", "proxy", "getweakrefcount", "getweakrefs", "WeakKeyDictionary", "ReferenceType", "ProxyType", "CallableProxyType", "ProxyTypes", "WeakValueDictionary", - "WeakSet"] + "WeakSet", "finalize"] class WeakValueDictionary(collections.MutableMapping): @@ -383,3 +385,91 @@ d[ref(key, self._remove)] = value if len(kwargs): self.update(kwargs) + + +class finalize(object): + """Class for finalization of weakrefable objects + + finalize(obj, func, *args, **kwds) returns a callable finalizer + object which will be called when obj is garbage collected. The + first time the finalizer is called it evaluates func(*arg, **kwds) + and returns the result. After this the finalizer is dead, and + calling it just returns None. + + When the program exits any remaining finalizers for which the + atexit attribute is true will be run in reverse order of creation. + By default atexit is false. + """ + __slots__ = ("atexit", "_count") + _registry = {} + _shutdown = False + _class_count = 0 + + def __init__(self, obj, func, *args, **kwds): + self.atexit = False + self._count = finalize._class_count + finalize._class_count += 1 + if obj is None: + wr = None + else: + wr = ref(obj, self._make_wrapper()) + self._registry[self] = (wr, func, args, kwds) + + def __call__(self): + """If the finalizer is dead return None. Otherwise, mark it as dead + and return the result of calling func(*args, **kwds). + """ + tmp = self._registry.pop(self, None) + if tmp and not self._shutdown and self._check(): + wr, func, args, kwds = tmp + return func(*args, **kwds) + + def pop(self): + """If the finalizer is dead return None. Otherwise, mark it as dead + and return the tuple (wr, func, args, kwds) where wr is a weak + reference to obj. + """ + return self._registry.pop(self, None) + + def get(self): + """If the finalizer is dead return None. Otherwise, return the tuple + (wr, func, args, kwds) where wr is a weak reference to obj. + """ + return self._registry.get(self, None) + + def _make_wrapper(self): + # Return a wrapper for self which can be used as weakref callback + def wrapper(wr): + try: + self() + except: + # Print traceback with first two frames stripped + sys.stderr.write("Ignored exception in finalizer - ") + t, v, tb = sys.exc_info() + tb = tb and tb.tb_next and tb.tb_next.tb_next + v.__traceback__ = tb + sys.excepthook(t, v, tb) + return wrapper + + def _sortkey(self): + # Used by _atexit_callback() - overridable by subclasses + return (0, self._count) + + def _check(self): + # Overridable by subclasses + return True + + @classmethod + def _atexit_callback(cls): + # At shutdown invoke finalizers for which atexit is true + try: + L = [f for f in cls._registry.keys() if f.atexit] + L.sort(key=lambda obj : obj._sortkey(), reverse=True) + for f in L: + f._make_wrapper()(None) + finally: + # prevent any more finalizers from executing during shutdown + cls._shutdown = True + cls._registry.clear() + +atexit.register(finalize._atexit_callback)