ActiveState Code

Recipe 391199: with-open-file block in Python


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
 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

Discussion

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.

Comments

  1. 1. At 8:32 p.m. on 10 mar 2005, Sakesun Roykiattisak said:

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

  2. 2. At 8:45 a.m. on 12 mar 2005, Chris Perkins said:

    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
    
  3. 3. At 1:02 a.m. on 14 mar 2005, Carl Banks (the author) said:

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

Sign in to comment