Welcome, guest | Sign In | My Account | Store | Cart

One can (ab)use this decorator to get the effect of a Lisp-like with_open_file block in Python. Kind of. Requires Python 2.4.

Python, 30 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# Define a "decorator" that, rather than decorating the function,
# calls it, passing it a file object that it had opened as the first
# argument.  Of course, it makes sure to close the file upon the
# function's return (or nonreturn) with a try...finally block. 
# The decorator returns None because we never want to call the
# decorated function ever again; we're using it as a code block.

def call_with_open_file(filename):
    def with_open_file(func):
         flo = open(filename)
         try: func(flo)
         finally: flo.close()
         return None
    return with_open_file


# Example of (ab)use

import sys

@call_with_open_file("readme.txt")
def print_readme(flo):
    for line in flo:
        sys.stdout.write(line)


# print_readme is None afterwards, so we don't accidentlly
# call it again.

assert print_readme is None

In Common Lisp, there is a very convenient macro called with-open-file that opens a file, binds the file object to a block-local variable, and then executes the block. The file is automatically closed when that block is exited.

Not having macros in Python, we generally do this with a try...finally statement, which works fine.

But let's try to (ab)use decorators to get a with-open-file block anyways. The call_with_open_file decorator kind of gives us this effect: the decorated function acts as a code block. However, unlike try...finally, it has the limitations of nested functions, so unfortunately this recipe is not quite so useful as the Lisp macro.

Nevertheless, it might stil be useful in the occasional sitation where you have to use libraries that don't let you create and finalize objects yourself, but force you to use a callback. Having no way to use a try...finally, and needing to reference (but hopefully not rebind) local variables, a decorator like the one in this recipe could help.

3 comments

Sakesun Roykiattisak 16 years, 9 months ago  # | flag

Broken for sure... I'm not sure what the author try to do, but the code is indeed broken.

Chris Perkins 16 years, 9 months ago  # | flag

Love it. Abusing syntax is fun! This is almost like having Ruby code blocks.

def each(seq):
    def _each(func):
        for item in seq: func(item)
    return _each

@each(range(3))
def _(i):
    print 'Item: %r' % i

Item: 0
Item: 1
Item: 2
Carl Banks (author) 16 years, 9 months ago  # | flag

two possibilities. First, it requires Python 2.4. I've added a note. Second, there was a slight bug in it, which I corrected.

Created by Carl Banks on Thu, 10 Mar 2005 (PSF)
Python recipes (4591)
Carl Banks's recipes (4)

Required Modules

Other Information and Tasks