Context manager protocol extension (2nd attempt)

I actually quite like the idea of __with__ (and of course __awith__): it seems much easier to get right on the first try than the existing __[a]enter__/__[a]exit__ pairs. I am somewhat concerned that there could be mines in the finalization of the generator that I can’t see, and I would like someone more knowledgeable about that area to chime in before I would be a full +1 on the proposal.

Reading a bit of the linked past discussion around PEP 707, @storchaka brought up there a couple of very good points about current limitations of __enter__/__exit__ that can be addressed by __with__, including currently not being able to return something from __exit__, which uses the return value to determine whether to suppress an exception, and not having access to the return value of __enter__ in __exit__. With __with__ the equivalent of the return value of __enter__ is of course still available, and it would be natural to use the return value as the result of exiting the context. There would need to be new syntax to use it, though. My immediate thought there is to borrow the return annotation syntax:

class some_context:
    def __with__(self):
        a = 1
        # Is there a use case for passing something back into the context?
        #x = yield a
        yield a
        return a + 1  # or a + (x if x is not None else 1)?

with some_context() as a -> b:
    assert a == 1
    try:
        b
    except NameError:
        pass
    else:
        assert False, "using 'b' within the context should raise"
assert a == 1
assert b == 2
# If `__with__` raises, `b` should still be unbound

I don’t believe that -> is the right syntax to use, since it really has no relation to its use as a return annotation, but this at least conveys the idea.

I would be willing to sponsor a PEP on this topic. I’m not certain of its acceptance, but I’d like to see it tried :slight_smile:

13 Likes