Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork32k
Description
Suppose we define a context manager, in the usual way:
@contextmanagerdefctx():print("enter")yieldprint("exit")defclassic():withctx():print("body")classic()# enter, body, exit
We can also use it as a decorator, thanks to theconvenientContextDecorator
class used by@contextmanager
:
@ctx()deffn():print("body")fn()# enter, body, exit
...but if we naively do the same thing to a generator or an async function, the equivalence breaks down:
@ctx()defgen():print("body")yieldfor_ingen(): ...# enter, exit, body!@ctx()asyncdefafn():print("body")awaitafn()# enter, exit, body!
This seems pretty obviously undesirable, so I think we'll want to changeContextDecorator.__call__
. Possibilities include:
branch on iscoroutinefunction / isgeneratorfunction / isasyncgenfunction, creating alternative
inner
functions to preserve the invariant that@ctx()
is just like writingwith ctx():
as the first line of the function body. In a quick survey, this is the expected behavior, but also a change from the current effect of the decorator.instead of branching, warn (and after a few years raise) when decorating a generator or async function.
- alternative implementation: inspect the return value inside
inner
, to detect sync wrappers of async functions. I think the increased accuracy is unlikely to be worth the performance cost. - we could retain the convenience of using a decorator by defining
ContextDecorator.gen()
,.async_()
, and.agen()
methods which explicitly support wrapping their corresponding kind of function.
- alternative implementation: inspect the return value inside
We'll also want applying anAsyncContextDecorator
to an async generator function to match whatever we decide forContextDecorator
on a sync generator.