[Baypiggies] Conditional Context Managers

Andrew Dalke dalke at dalkescientific.com
Tue Oct 27 19:22:18 CET 2009


On Oct 27, 2009, at 5:59 PM, <Reitmeyer_Richard at emc.com> wrote:
> How do people do conditional "with" statements?

I've not yet had to deal with that case, but to point out

> Question and possible answers:
> 1) Repeat the guts, w/ possible refactor of guts.

> 2) Make "trinary with" using the and/or hack.
>
>     def foo4(file_obj_or_name):
>         file_obj = file_obj_or_name
>         with isinstance(file_obj, str) and \
>           open(file_obj_or_name) or file_obj as file_obj:
>             retval = foo_guts(file_obj)
>
>         return retval
>

Mmm, that doesn't work. I thought the point was that you didn't want  
to close file_obj if the input wasn't a filename, but the file  
context manager will do that for you. Here's my test program (with a  
few bug fixes)

import sys

import sys # this is the first line
# This is the second line

def process(file_obj_or_name):
     with isinstance(file_obj_or_name, str) and \
             open(file_obj_or_name) or file_obj_or_name as file_obj:
         print "First line", repr(file_obj.readline())
     sys.stdout.write("Finished with %r\n" % (file_obj_or_name,))

process("/etc/passwd")
f = open(__file__)
process(f)
process(f)


Running it gives me:

First line '##\n'
Finished with '/etc/passwd'
First line 'import sys # this is the first line\n'
Finished with <closed file 'conditional_with.py', mode 'r' at 0x61e6b0>
Traceback (most recent call last):
   File "conditional_with.py", line 25, in <module>
     process(f)
   File "conditional_with.py", line 18, in process
     open(file_obj_or_name) or file_obj_or_name as file_obj:
ValueError: I/O operation on closed file



> 3) Write a entire context manager.



There are at least two other solutions:

4) Break it up into a subfunction, which is rather like your #1 with  
refactoring


import sys # this is the first line
# This is the second line

def process(file_obj_or_name):
     if isinstance(file_obj_or_name, str):
         with open(file_obj_or_name) as file_obj:
             _process(file_obj)
     else:
         _process(file_obj_or_name)
     sys.stdout.write("Finished with %r\n" % (file_obj_or_name,))

def _process(file_obj):
     print "First line", repr(file_obj.readline())


process("/etc/passwd")
f = open(__file__)
process(f)
process(f)





5) Use a function which returns a simpler context manager only for  
the existing file handle case

import sys # this is the first line
# This is the second line

class Nonclosing(object):
     def __init__(self, fileobj):
         self.fileobj = fileobj
     def __enter__(self, *args):
         return self.fileobj
     def __exit__(self, *args):
         pass

def _open(file_obj_or_name):
     if isinstance(file_obj_or_name, str):
         return open(file_obj_or_name)
     return Nonclosing(file_obj_or_name)

def process(file_obj_or_name):
     with _open(file_obj_or_name) as file_obj:
         print "First line", repr(file_obj.readline())
     sys.stdout.write("Finished with %r\n" % (file_obj_or_name,))

process("/etc/passwd")
f = open(__file__)
process(f)
process(f)

the output of which is

First line '##\n'
Finished with '/etc/passwd'
First line 'import sys # this is the first line\n'
Finished with <open file 'conditional_with.py', mode 'r' at 0x61e6b0>
First line '# This is the second line\n'
Finished with <open file 'conditional_with.py', mode 'r' at 0x61e6b0>



Of these I prefer #4 because it's the least number of lines of code,  
but it does require more code rewriting. Otherwise #5 is I think a  
good alternative, and it works better over a wider range of problems.





				Andrew
				dalke at dalkescientific.com




More information about the Baypiggies mailing list