[Python-Dev] Object finalization for local (ie function) scopes

Oliver Schoenborn oliver.schoenborn at utoronto.ca
Sun Jun 13 14:49:08 EDT 2004


> > The technique itself can be summarized as follows:
> >
> > - Create a function that wraps a try/finally around the function of
interest
> > - Make class whose instances need "cleanup" automatically tell the
function
> > in which they were created (the "function of interest" above) that they
> > should be cleaned up upon function exit. This requires three things:
>
> I claim that this might not be possible to do across different
> implementations of Python. It relies on the ability to look at the local
> variables of a caller of a function, and the Python language does not
> guarantee support for such a mechanism.

Actually it doesn't. The file I showed in my first post to python-dev used
the stack, plus the knowledge of what name was given to a local variable,
but it didn't need to "look at the local variables"; it looked at one
variable, of known name. I have a second implementation, below, that
requires neither stack, nor local, and is faster.

> > tied to the function. But there are other possibilities. E.g. instead of
> > creating a function attribute, the ScopeGuardian could use a global
stack of
> > list of objects. An instance of NeedsFinalization would use the top list
of
> > the stack to add a reference to istelf there.
>
> In that implementation, how would you know how many objects to cleanup?

See my reply to your last post where you ask same question. However, as to
this new implementation, I paste it in at the end of the post.

> Yes, but I have no reason to believe you an implementation is possible
> until I have seen an implementation.

> It might be that an implementation of the feature would be very
> expensive even if not used, in which case the feature also would not
> be acceptable for inclusion in Python.

I'm doing some timing tests, I should have them tonight with a bit of luck.

> However, this is probably the time to ask for a PEP that properly
> describes the API and the semantics of the feature.

In time... I'd like a couple more opinions if possible. I'm surprised there
hasn't been more given how fundamental the problem of RAII is in Python.

Oliver

----------- New implementation for scope.py --------------
# Note: comments and tests are in module, but not reproduced here for space

import sys

def ScopeGuarded(func):
    return lambda *args, **kwargs: ScopeGuardian(func, *args, **kwargs)

_funcStack = []

class NeedsFinalization:
    def __init__(self):
        print '\n%s: being created' % repr(self)
        self.__finalized = False
        try: _funcStack[-1].append(self)
        except IndexError:
            raise RuntimeError, "Forgot to scope-guard function? "

    def finalizeMaster(self):
        print '%s: Finalize() being called' % repr(self)
        self._finalize()
        self.__finalized = True

    def __del__(self):
        try:
            problem = not self.__finalized
        except AttributeError:
            msg = '%s: NeedsFinalization.__init__ not called for %s' \
                  % (repr(self), self.__class__)
            raise RuntimeError, msg

        if not problem:
            print '%s: Finalized properly' % repr(self)
        else:
            print "Forgot to scope-guard function"

def ScopeGuardian(func, *args, **kwargs):
    try:
        scopedObjs = []
        _funcStack.append(scopedObjs)

        func(*args, **kwargs)
        print 'Scoped variables created during call to %s: %s' \
              % (func, scopedObjs)

    finally:
        _funcStack.pop()
        scopedObjs.reverse() # destroy in reverse order from creation
        for obj in scopedObjs:
            obj.finalizeMaster()


def testBasic():
    def func1():
        ok = TestFree()
        danger = TestDanger()

    # test when forget to use ScopeGuardian
    try:
        hello = func1()
        assert False, 'Expected exception not thrown!'
    except RuntimeError, msg:
        print 'Expected exception caught: ', msg

    func1 = ScopeGuarded(func1)
    func1()

    def func2(objType):
        dontKnow = objType()

    func2 = ScopeGuarded(func2)
    print "An RuntimeError exception will happen but be ignored: "
    func2(TestDangerNoInit)
    func2(TestDanger)

def testRecursive():
    def recurse(n):
        print 'Recurse(%s)' % n
        danger = TestDanger()
        ok = TestFree()
        if n>0:
            recurse(n-1)
        else:
            print 'recurse: Raising exception'
            raise RuntimeError, "pretend exception thrown during recursion"

    print '\nTesting that recursive does not work'
    recurse = ScopeGuarded(recurse)
    try:
        recurse(3)
        assert False, 'Expected exception not thrown!'
    except RuntimeError, msg:
        print 'Expected exception caught: ', msg

if __name__ == '__main__':

    class TestFree:
        def _finalize():
            raise RuntimeError, 'finalize() not supposed to be called'

    class TestDanger(NeedsFinalization):
        def __init__(self):
            NeedsFinalization.__init__(self)

        def _finalize(self):
            """Override this. If you class inherits from a
            class derived from NeedsFinalization, make sure to
            call parent.finalize()."""
            pass

    class TestDangerNoInit(NeedsFinalization):
        def __init__(self):
            pass
        def _finalize(self):
            pass

    testBasic()
    testRecursive()




More information about the Python-Dev mailing list