[Python-Dev] PEP 550 v4

Yury Selivanov yselivanov.ml at gmail.com
Sun Aug 27 11:19:20 EDT 2017


On Sun, Aug 27, 2017 at 6:08 AM, Stefan Krah <stefan at bytereef.org> wrote:
> On Sat, Aug 26, 2017 at 04:13:24PM -0700, Nathaniel Smith wrote:
>> On Sat, Aug 26, 2017 at 7:58 AM, Elvis Pranskevichus <elprans at gmail.com> wrote:
>> > What we are talking about here is variable scope leaking up the call
>> > stack.  I think this is a bad pattern.  For decimal context-like uses
>> > of the EC you should always use a context manager.  For uses like Web
>> > request locals, you always have a top function that sets the context
>> > vars.
>>
>> It's perfectly reasonable to have a script where you call
>> decimal.setcontext or np.seterr somewhere at the top to set the
>> defaults for the rest of the script.
>
> +100.  The only thing that makes sense for decimal is to change localcontext()
> to be automatically async-safe while preserving the rest of the semantics.

TBH Nathaniel's argument isn't entirely correct.

With the semantics defined in PEP 550 v4, you still can set decimal
context on top of your file, in your async functions etc.

This will work:

    decimal.setcontext(ctx)

    def foo():
         # use decimal with context=ctx

and this:

    def foo():
         decimal.setcontext(ctx)
         # use decimal with context=ctx

and this:

    def bar():
        # use decimal with context=ctx

    def foo():
         decimal.setcontext(ctx)
         bar()

and this:

    def bar():
        decimal.setcontext(ctx)

    def foo():
         bar()
         # use decimal with context=ctx

and this:

    decimal.setcontext(ctx)

    async def foo():
         # use decimal with context=ctx

and this:

    async def bar():
        # use decimal with context=ctx

    async def foo():
         decimal.setcontext(ctx)
         await bar()

The only thing that will not work, is this (ex1):

    async def bar():
        decimal.setcontext(ctx)

    async def foo():
         await bar()
         # use decimal with context=ctx

The reason why this one example worked in PEP 550 v3 and doesn't work
in v4 is that we want to avoid random code breakage if you wrap your
coroutine in a task, like here (ex2):

    async def bar():
        decimal.setcontext(ctx)

    async def foo():
         await wait_for(bar(), 1)
         # use decimal with context=ctx

We want (ex1) and (ex2) to work the same way always.  That's the only
difference in semantics between v3 and v4, and it's the only sane one,
because implicit task creation is an extremely subtle detail that most
users aren't aware of.  We can't have a semantics that let you easily
break your code by adding a timeout in one await.

Speaking of (ex1), there's an example that didn't work in any PEP 550 version:

    def bar():
        decimal.setcontext(ctx)
        yield

    async def foo():
         list(bar())
         # use decimal with context=ctx

In the above code, bar() generator sets some decimal context, and it
will not leak outside of it.  This semantics is one of PEP 550 goals.
The last change just unifies this semantics for coroutines,
generators, and asynchronous generators, which is a good thing.

Yury


More information about the Python-Dev mailing list