On Mon, 18 Nov 2019 at 15:54, Paul Moore <p.f.mo...@gmail.com> wrote:
>
> On Mon, 18 Nov 2019 at 11:12, Oscar Benjamin <oscar.j.benja...@gmail.com> 
> wrote:
>
> > I am proposing the root of the problem here is the fact that open
> > acquires its resource (the opened file descriptor) before __enter__ is
> > called. This is what I mean by a context manager that "misbehaves". If
> > there was a requirement on context managers that __exit__ cleans up
> > after __enter__ and any resource that needs cleaning up should only be
> > acquired in __enter__ then there would never have been a problem with
> > nested.
> [...]
> > What I am saying is that conceived as a context manager the object
> > returned by open misbehaves. I think that not just nested but a number
> > of other convenient utilities and patterns could have been possible if
> > opened has been used instead of open and if context managers were
> > expected to meet the constraint:
> > """
> > There should be no need to call __exit__ if __enter__ has not been called.
> > """
> > Of course a lot of time has passed since then and now there are
> > probably many other misbehaving context managers so it might be too
> > late to do anything about that.
>
> Hi Oscar,
> Thanks for the explanation. I see what you mean now, and that *was*
> something I got from the previous discussion, it's just that I guess
> I'm so used to the current behaviour that I never really thought of it
> as "misbehaviour". I'm not 100% convinced that there aren't edge cases
> where even your strengthened requirements on a context manager might
> not be enough. For example, if __enter__ is called, but raises an
> exception, is calling __exit__ required then?

It has never been the case that __exit__ would be called if __enter__
does not exit successfully even for the basic form of the with
statement e.g.:

class ContextMgr:
    def __enter__(self):
        print('Entering...')
        raise ValueError('Bad stuff')
    def __exit__(self, *args):
        print('Exiting')

with ContextMgr():
    pass

Gives

$ python f.py
Entering...
Traceback (most recent call last):
  File "f.py", line 8, in <module>
    with ContextMgr():
  File "f.py", line 4, in __enter__
    raise ValueError('Bad stuff')
ValueError: Bad stuff

You can also see this in the original specification of the with
statement since __enter__ is called outside the try suite:
https://www.python.org/dev/peps/pep-0343/#specification-the-with-statement

> Consider
>
> @contextmanager
> def open_2_files():
>     f = open("file1")
>     g = open("file2")
>     try:
>         yield (f,g)
>     finally:
>         g.close()
>         f.close()
>
> That meets your criterion, but if open("file2") fails, you're still in
> a mess. Of course, that's a toy example, and could be written to fix
> that,

That example is a poor context manager by anyone's definition and can
easily be fixed:

@contextmanager
def open_2_files():
    with open('file1') as f:
        with open('file2') as g:
            yield (f, g)

> and we could even close that loophole by saying "a context
> manager should only manage one resource", but we can probably carry on
> down that route for quite a while (and "should only manage one
> resource" is not actually correct - the whole *point* of something
> like nested() would be to manage multiple resources).

I don't see why you would say that managing multiple resources is a
problem here. It's a question of who is responsible for what. The
context manager itself is responsible for cleaning up anything if an
exception is raised *inside* it's __enter__ and __exit__ methods. Once
the manager returns from __enter__ though it hands over control. Then
the with statement and other supporting utilities are responsible for
ensuring that __exit__ is called at the appropriate later time.

The problem with a misbehaving context manager is that it creates a
future need to call __exit__ before it has been passed to a with
statement or any other construct that can guarantee to do that.

--
Oscar
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/YRRWR5JV7BTTB46ULCP6ZU57S74IPVX3/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to