[Python-Dev] On suppress()'s trail blazing (was Re: cpython: Rename contextlib.ignored() to contextlib.ignore())

Guido van Rossum guido at python.org
Fri Oct 18 01:02:28 CEST 2013


On Thu, Oct 17, 2013 at 3:51 PM, Oscar Benjamin
<oscar.j.benjamin at gmail.com>wrote:

> On 17 October 2013 20:01, Guido van Rossum <guido at python.org> wrote:
> > On Thu, Oct 17, 2013 at 11:55 AM, Oscar Benjamin
> > <oscar.j.benjamin at gmail.com> wrote:
> >>
> >> On 17 October 2013 19:40, Xavier Morel <python-dev at masklinn.net> wrote:
> >> > I think there's already a significant split between context managers
> >> > which handle the lifecycle of a local resource (file, transaction) and
> >> > those which purport to locally alter global-ish state (cwd,
> >> > decimal.localcontext, logging.captureWarnings, redirect_stdout).
> >> >
> >> > And the latter worries me (much more than the very localized behavior
> of
> >> > suppress) because I don't see any way to implement them safely and
> >> > correctly when mixing it with coroutines in today's Python (some of
> them
> >> > aren't even thread-safe), all of that while I expect coroutines will
> see
> >> > significantly more use in the very near future with yield from and
> >> > tulip's promotion of coroutine-style async.
> >>
> >> I maybe misunderstanding how the  coroutine-style async works but I
> >> would have thought that it would be as simple as: don't use
> >> global-state-restoring-context-managers around statements that yield
> >> control (it would be simpler if there was a good term for describing
> >> that kind of CM). That's simpler to implement and computationally
> >> cheaper than e.g. the thread-local state used by the decimal module.
> >
> > Context managers that actually save and restore *global* state are
> already
> > not thread-safe, so concluding they are also not coroutine-safe (or
> > task-safe?) seems a small step.
> >
> > I'd be more worried about context manager that use thread-local state --
> > there is no similar concept in Tulip.
>
> It's unnecessary in Tulip. The need for thread-local state in e.g.
> decimal contexts is driven by the fact that multi-threaded execution
> switches in an uncontrollable way. Tulip specifically makes it
> possible to control the points at which a switch occurs making this
> safe (even if localcontext() wasn't thread-safe):
>
> with decimal.localcontext() as ctx:
>     ctx.prec = 100
>     c = a + b
>     # more synchronous decimal calculations
>
> # State is restored before allowing other code to execute
> yield from save_in_database(c)
>
> So it's fine to use global/thread-local state modifying/restoring
> context managers in Tulip as long as you don't yield control to other
> code within the with block. (unless I misunderstand - I lost track of
> Tulip some time ago).
>

You've got it exactly right.


> The issue with decimal.localcontext() and yield arises when using
> generators as much as coroutines e.g.:
>
> def exact_sum(nums):
>     start = Decimal(0)
>     with decimal.localcontext() as ctx:
>         ctx.traps[decimal.Inexact] = True
>         for num in nums:
>             try:
>                 total += Decimal(num)
>             except decimal.Inexact:
>                 ctx.prec *= 2
>     return total
>
> The above is fine for computing the sum of a list of
> Decimals/ints/floats. However it fails if you pass in a generator that
> carelessly modifies the arithmetic context around yield calls:
>
> def compute():
>     with decimal.localcontext() as ctx:
>         ctx.prec = 15
>         ctx.traps[decimal.Inexact] = False
>         yield a + b
>         yield b - c
>         # etc.
>
> exact_sum(compute())
>
> There needs to be a convention that either functions like exact_sum()
> mustn't assume continuity of the context between iterations or a
> function like compute() must restore before yielding. IMO the only
> sane approach for async coroutines is to say that if you yield or
> yield from then it is your responsibility to restore any temporarily
> altered global/thread-local state first.
>

Right again. The simplest rule to remember seems to be "don't use yield or
yield-from inside a with-statement". You can relax it by limiting it to
context managers that manage any kind of shared resource, but that is
probably already too subtle: e.g. yielding inside "with open(file) as f"
seems fine, but yielding inside "with lock" is problematic, since the other
side might try to acquire the same lock, and deadlock.

-- 
--Guido van Rossum (python.org/~guido)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-dev/attachments/20131017/015918d2/attachment.html>


More information about the Python-Dev mailing list