[Python-ideas] Make "yield" inside a with statement a SyntaxError

Oscar Benjamin oscar.j.benjamin at gmail.com
Wed Aug 8 11:55:55 EDT 2018


On 8 August 2018 at 15:37, Oscar Benjamin <oscar.j.benjamin at gmail.com> wrote:
> On 8 August 2018 at 15:22, Ronald Oussoren via Python-ideas
> <python-ideas at python.org> wrote:
>>
>> It is also possible to fix the particular issue by using another with statement, that is use:
>>
>> with contextlib.closing(read_multiple(…)) as chunks:
>>    for contents in chunks:
>>>
> That's a very good point Ronald. Having seen this a few times I can't
> think of any cases that wouldn't be solved by this.

Thinking about this some more: closing() can ensure finalisation but
it is still generally bad to yield from a with block. Some context
managers are designed to temporarily alter global state between
__enter__ and __exit__ - this can be very confusing if you use them
around a yield block.

As an example the localcontext context manager from the decimal module
does this. So if you do something like

def decimal_generator():
    ...
    with decimal.localcontext() as ctx:
        ctx.prec = 4   # Use 4 digits for calculations temporarily
        yield x + y
        yield y + z


with decimal.localcontext() as ctx:
    ctx.prec = 10   # Use 10 digits for calculations temporarily
    total = decimal.Decimal(0)
    for item in decimal_generator():
        total += item

Here the line total += item will end up using 4 digit arithmetic
rather than 10. That's because the yield allows the "local" context to
leak out. The fix for that is something like:

def decimal_generator():
    ...
    ctx = decimal.localcontext()
    ctx.prec = 4   # Use 4 digits for calculations temporarily
    with ctx:
        val = x + y
    yield val
    with ctx:
        val = y + z
    yield val

I still think that this is a good idea though:

> If a language change was wanted for this then perhaps it should be to
> do like file objects and add the __exit__ (calls close()) method to
> generator objects so that you can omit the closing and just do:
>
>     with read_multiple(…) as chunks:
>         for contents in chunks:
>             ...

--
Oscar


More information about the Python-ideas mailing list