Block-structured resource handling via decorators

John Perks and Sarah Mount johnandsarah at estragon.freeserve.co.uk
Sat Jul 30 07:21:39 EDT 2005


> The only cases I see the first school of thought is when the resource
> in question is "scarce" in some way.

By "resource" I meant anything with some sort of acquire/release
semantics. There may be plenty of threading.Locks available, but it's
still important that a given Lock is released when not needed.

For example, most OS's place a

> > class withFile(blockScopedResource):
> >     init, cleanup = open, 'close'
> Well, I'd say that using a string for cleanup and a function for init
> is unpythonic.

I could have specified cleanup as lambda f:f.close(), but as I thought
it might be quite common to call a method on the resourse for cleanup,
if a string is specified a method of that name is used instead.

> The question is whether having to turn your scope into a
> function to do this is more trouble than it's worth.

Needing one slightly contrived-looking line (the def) vs a try-finally
block with explicit cleanup code? I know which I'd prefer, but for all I
know I could in a minority of 1 here.


> I'd certainly be interested in seeing the implementation.

And so you shall...

I start with the base class. It does all the work, everything else is
just tweaks for convenience. Normally, then, you wouldn't need to bother
with all the __init__ params.

class blockScopedResource(object):
    def __init__(self, init, cleanup,
        initArgs, initKwargs, cleanupArgs, cleanupKwargs,
        passResource, resourceIsFirstArg):

        self.init = init # function to get resource
        self.cleanup = cleanup # function to release resource
        self.initArgs, self.initKwargs = initArgs, initKwargs
        self.cleanupArgs, self.cleanupKwargs = cleanupArgs,
cleanupKwargs
        self.passResource = passResource # whether resource is passed
into block
        self.resourceIsFirstArg = resourceIsFirstArg # whether resource
is arg to init,
        # rather than returned from it

    def __call__(self, block):
        resource = self.init(*self.initArgs, **self.initKwargs)
        if self.resourceIsFirstArg:
             resource = self.initArgs[0]

        try:
            if self.passResource:
               block(resource)
            else:
               block()
        finally:
            self.cleanup(resource, *self.cleanupArgs,
**self.cleanupKwargs)

But this still won't do conveniently for files and locks, which are my
motivating examples.

The simpleResource class constructor gets its setup from attributes on
the type of the object being created, with sensible defaults being set
on simpleResource itself. As stated above, if a string is supplied as
init or cleanup, it is treated as a method name and that method is used
instead.

def stringToMethod(f):
    # Getting the attribute from the class may have wrapped it into
    # an unbound method; in this case, unwrap it
    if isinstance(f, types.MethodType) and f.im_self is None:
        f = f.im_func
    if not isinstance(f, basestring): return f
    def helper(resource, *args, **kwargs):
        return getattr(resource, str(f))(*args, **kwargs)
    return helper

class simpleResource(blockScopedResource):
    def __init__(self, *initArgs, **initKwargs):
        # get attributes off type
        t = type(self)
        blockScopedResource.__init__(self,
            stringToMethod(t.init), stringToMethod(t.cleanup),
            initArgs, initKwargs, t.cleanupArgs, t.cleanupKwargs,
            t.passResource, t.resourceIsFirstArg)

    # defaults supplied here
    cleanupArgs, cleanupKwargs = (), {}
    passResource = True
    resourceIsFirstArg = False


Then useful implementations can be written by:

class withFile(simpleResource):
    init, cleanup = open, 'close'

class withLock(simpleResource):
    init, cleanup = 'acquire', 'release'
    passResource = False
    resourceIsFirstArg = True


And new ones can be created with a similar amount of effort.

Of course, one-liners can be done without using the decorator syntax:

withLock(aLock)(lambda:doSomething(withAnArg))

Gotcha: If you stack multiple resource-decorator it won't do what you
want:

# !!! DOESN'T WORK !!!
@withLock(aLock)
@withLock(anotherLock)
def do():
    # ...

Either nest them explicitly (causing your code you drift ever further to
the right):
@withLock(aLock)
def do():
    @withLock(anotherLock)
    def do():
        # ...

Or come up with a multiple-resource handler, which shouldn't be too
hard:

@withResources(withLock(aLock), withLock(anotherLock),
withFile('/dev/null'))

But I'll get round to that another day.







More information about the Python-list mailing list