On Sat, Mar 14, 2009 at 9:58 PM, Nick Coghlan <ncoghlan@gmail.com> wrote:
I missed the discussion about potentially adding syntactic support for
multiple context managers in the with statement, but figured I should
mention a real limitation of contextlib.nested that *would* be fixed by
adding dedicated syntactic support.

There's a genuine semantic difference between this:

 with cmA():
     with cmB():
         # whatever

and this:

 with nested(cmA(), cmB()):
     # whatever

The latter is actually more accurately translated as:

 mgr1, mgr2 = cmA(), cmB():
 with mgr1:
     with mgr2:
         # whatever

That is, when using nested() the later context managers are created
outside the scope of the earlier context managers.

So, to use Christian's example from the previous discussion:

 with lock:
     with open(infile) as fin:
         with open(outfile, 'w') as fout:
             fout.write(fin.read())

Using contextlib.nested for that would be outright broken:

 with nested(lock, open(infile), open(outfile) as (_, fin, fout):
     fout.write(fin.read())

1. The files are opened without acquiring the lock first
2. If an IOError is raised while opening "outfile", then "infile"
doesn't get closed immediately

I created issue 5491 [1] to point out that the contextlib.nested docs
could do with being tweaked to make this limitation clearer.

Dedicated syntax (such as the form that Christian proposed) would fix
this problem:

 with lock, (open(infile) as fin), (open(outfile, 'w') as fout):
     fout.write(fin.read())

Of course, a custom context manager doesn't suffer any problems either:

   @contextmanager
   def synced_io(lock, infile, outfile):
       with lock:
           with open(infile) as fin:
               with open(outfile) as fout:
                   yield fin, fout

   with synced_io(lock, infile, outfile) as (fin, fout):
      fout.write(fin.read())

fwiw, I believe you could write a version of nested that generates the above code based on the parameters but I believe it'd be disgustingly slow...

@contextmanager
def slow_nested(*args):
  code_lines = []
  vars = []
  code_lines.append('@contextmanager')
  code_lines.append('def _nested(*args):')
  for idx in xrange(len(args)):
    vars.append('c%d' % idx)
    code_lines.append('%swith args[%d] as %s:' % (' '*(idx+1), idx, vars[-1]))
  code_lines.append('%syield %s' % (' '*(len(args)+1), ','.join(vars)))
  code = '\n'.join(code_lines)
  print 'CODE:\n', code
  exec(code)
  yield _nested(*args)

 


Cheers,
Nick.

[1] http://bugs.python.org/issue5491


--
Nick Coghlan   |   ncoghlan@gmail.com   |   Brisbane, Australia
---------------------------------------------------------------
_______________________________________________
Python-ideas mailing list
Python-ideas@python.org
http://mail.python.org/mailman/listinfo/python-ideas