[Python-ideas] A real limitation of contextlib.nested()

Gregory P. Smith greg at krypto.org
Sun Mar 15 08:45:23 CET 2009


On Sat, Mar 14, 2009 at 9:58 PM, Nick Coghlan <ncoghlan at 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 at gmail.com   |   Brisbane, Australia
> ---------------------------------------------------------------
> _______________________________________________
> Python-ideas mailing list
> Python-ideas at python.org
> http://mail.python.org/mailman/listinfo/python-ideas
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20090315/d99232e3/attachment.html>


More information about the Python-ideas mailing list