I think that making return have two separate behaviours (returning from a function and sending a value to all enclosing context managers) is extremely awkward. It’s an additional data flow path that the reader has to scan.
This is the exact same reason I dislike making context managers also function as a condition.
I think it’s better to keep things simple: Use if or match if you want conditional behavior. Use a function call if you want the block to do something. Use return if you want to exit a function.
I don’t think saving one line of code (which is what two “features” do) is worth the enormous complexity they add.
I think the way this thing is gonna ever progress is to only write the transition from __enter__/__exit__ to __with__ in the PEP. The rest of the ideas will fall into the stalemate and hence Status quo wins a stalemate.
So, as I see only the __with__ is less controversial, other ideas could be the potential __with_ex__ later on based on __with__ behavior.
I agree, and I don’t think that’s the intention. A context manager can currently see an exception thrown by the block, but we don’t describe that as “sending an exception to all enclosing context managers”.
My point is that that is one data flow path that we already have. And Sascha’s proposal is to add another data flow path whereby returned values are also sent to all context managers.
I’ve only been skimming the recent parts of this discussion, but what’s the use case here? Why would a context manager want to see the value being returned from the function enclosing the with statement? Did I miss some justification for this feature? It honestly seems like a rather weird thing to want, to me. After all, the return means the code after the CM will never get control, so how will the value be used?
I agree that the proposal probably needs to be limited to only the yield-based __with__ protocol. I disagree about the __with_ex__, because I think that the other ideas can be incorporated as future improvements to __with__. These would fit in quite naturally:
The __with__ generator should yield exactly once, or else it is an error, but different behavior could be specified in the future for generators that yield more or fewer times
The value of the yield expression should be None, but a way to provide a different value could be added later
The return value of the generator would be ignored, but there could eventually be a way to get that value
Other ideas could be implemented as mixins or decorators
That’s the great advantage of the __with__ idea, that it can be extended this way without breaking any working code that uses __with__ in a similar way to the existing protocol, or like contextlib.contextmanager. It would already improve upon the current protocol (e.g. by allowing values to be kept for use after the with block ends), and also makes allowances for the other ideas, should there be demand for them.
I’m not sure I fully understand the benefit or use case here. It really kind of seems like changing the base context manager protocol is overengineering here. If a context manager needs access to the return value of the function, why not just change the contextmanager to yield something that can be modified? :
import contextlib
@contextlib.contextmanager
def fun_time():
context = {}
try:
yield context
finally:
if 'return_value' in context:
print("The return value is: " + str(context['return_value']))
if __name__ == '__main__':
with fun_time() as context:
context['return_value'] = 'Hello World!'
When ran:
C:\Users\csm10495\Desktop>python contextman.py
The return value is: Hello World!
Using that type of logic, you can accomplish basically every case where the context manager needs to do something with a return value after.