On 15 October 2017 at 20:45, Paul Moore <p.f.moore@gmail.com> wrote:
On 15 October 2017 at 06:43, Nick Coghlan <ncoghlan@gmail.com> wrote:
 
>     # Generator form
>     def _results_gen(data):
>         for item in data:
>             with adjusted_context():
>                 yield calculate_result(item)
>
>     results = _results_gen(data)
>
> Today, while these two forms look like they *should* be comparable, they're
> not especially close to being semantically equivalent, as there's no
> mechanism that allows for implicit context reversion at the yield point in
> the generator form.

I'll have to take your word for this, as I can't think of an actual
example that follows the pattern of your abstract description, for
which I can immediately see the difference.

Interestingly, thinking about the problem in terms of exception handling flow reminded me of the fact that having a generator-iterator yield while inside a with statement or try/except block is already considered an anti-pattern in many situations, precisely because it means that any exceptions that get thrown in (including GeneratorExit) will be intercepted when that may not be what the author really intended.

Accordingly, the canonical guaranteed-to-be-consistent-with-the-previous-behaviour iterator -> generator transformation already involves the use of a temporary variable to move the yield outside any exception handling constructs and ensure that the exception handling only covers the code that it was originally written to cover:

    def _results_gen(data):
        for item in data:
            with adjusted_context():
                result_for_item = calculate_result(item)
            yield result_for_item

    results = _results_gen(data)

The exception handling expectations with coroutines are different, since an "await cr" expression explicitly indicates that any exceptions "cr" fails to handle *will* propagate back through to where the await appears, just as "f()" indicates that unhandled exceptions in "f" will be seen by the current frame.

And even if as a new API context variables were to be defined in a yield-tolerant way, a lot of existing context managers still wouldn't be "yield safe", since they may be manipulating thread local or process global state, rather than context variables or a particular object instance.

Cheers,
Nick.

--
Nick Coghlan   |   ncoghlan@gmail.com   |   Brisbane, Australia