Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Every input in asyncio REPL (wrongly) runs in a separate PEP 567 context #124594

Closed
Labels
3.12only security fixes3.13bugs and security fixes3.14bugs and security fixestopic-asynciotopic-replRelated to the interactive shelltype-bugAn unexpected behavior, bug, or error
@johnslavik

Description

@johnslavik

Bug report

Bug description:

The bug is twofold, affects the asyncio REPL, and refers toPEP 567 contexts andcontext variables.

  1. Non-async inputs don't run in the same context.

    Details

    This is how we can use the standard Python REPL to demonstrate context variables (without asyncio tasks):

    Python 3.14.0a0 (heads/main-dirty:8447c933da3, Sep 25 2024, 15:45:56) [GCC 11.4.0] on linuxType "help", "copyright", "credits" or "license" for more information.>>> import contextvars>>> var = contextvars.ContextVar("var")>>> var.set("ok")<Token var=<ContextVar name='var' at ...> at ...>>>> var.get()'ok'

    I'd expect the asyncio REPL to be a slightly better tool for presenting them though. However, I ran into a problem with running the same sequence of instructions in that REPL, because it implicitly copies the context for every input and then runs the instructions in that copied context:

    asyncio REPL 3.14.0a0 (heads/main-dirty:8447c933da3, Sep 25 2024, 15:45:56) [GCC 11.4.0] on linuxUse "await" directly instead of "asyncio.run()".Type "help", "copyright", "credits" or "license" for more information.>>> import asyncio>>> import contextvars>>> var = contextvars.ContextVar("var")>>> var.set("ok")<Token var=<ContextVar name='var' at ...> at ...>>>> var.get()Traceback (most recent call last):  File ".../cpython/Lib/concurrent/futures/_base.py", line 448, in result    return self.__get_result()           ~~~~~~~~~~~~~~~~~^^  File ".../cpython/Lib/concurrent/futures/_base.py", line 393, in __get_result    raise self._exception  File ".../cpython/Lib/asyncio/__main__.py", line 40, in callback    coro = func()  File "<python-input-3>", line 1, in <module>    var.get()    ~~~~~~~^^LookupError: <ContextVar name='var' at ...>

    I assume it to just be a leftover of beforethecontext parameter for asyncio routines became a thing in 3.11.

  2. Async inputs don't run in the same context.
    Similar to the first part, but this applies to every coroutine being run in the REPL.

    Details

    By standard, a simple coroutinedoes not run in a separate context (in contrast totasks that document their handling of context). Consider the following script:

    importasynciofromcontextvarsimportContextVarvar=ContextVar("var",default="unset")asyncdefsetvar()->None:var.set("set")asyncdefmain()->None:awaitsetvar()# runs in *this* contextprint(var.get())# gets us "set", not "unset"asyncio.run(main())print(var.get())# coro passed to asyncio.run() runs in a copied context, which gets us "unset" here

    In the REPL, I'd expect that we're inside the "global" context ofasyncio.run()—what I mean by this is that the context we work in (for the entire REPL session) is the same one that is reused to run every asynchronous input, because we're in the same "main" task, and the point of asyncio REPL is to be able to omit it. While it is expected that every asynchronous input with top-levelawait statements becomes a task for the current event loop internally, I would imagine that the behavior resembles what I'd get by merging all the inputs together and putting them inside aasync def main() function in a Python script.
    For that reason, I consider the following a part of the bug:

    asyncio REPL 3.14.0a0 (heads/main-dirty:8447c933da3, Sep 25 2024, 15:45:56) [GCC 11.4.0] on linuxUse "await" directly instead of "asyncio.run()".Type "help", "copyright", "credits" or "license" for more information.>>> import asyncio>>> import contextvars>>> var = contextvars.ContextVar("var")>>> async def setvar():...     var.set("ok")...     >>> await setvar()>>> var.get()Traceback (most recent call last):  File ".../cpython/Lib/concurrent/futures/_base.py", line 448, in result    return self.__get_result()           ~~~~~~~~~~~~~~~~~^^  File ".../cpython/Lib/concurrent/futures/_base.py", line 393, in __get_result    raise self._exception  File ".../cpython/Lib/asyncio/__main__.py", line 40, in callback    coro = func()  File "<python-input-4>", line 1, in <module>    var.get()    ~~~~~~~^^LookupError: <ContextVar name='var' at ...>

    I'd expect every asynchronous input to not run in an implicitly copied context, because that's what feels like if we were writing a hugeasync def main() function—everyawait does not create a task, it just awaits the coroutine (the execution of which is managed by the event loop).

    Instead of comparing to a hugeasync def main(), I could say that we're reusing the same event loop over and over, and that can theoretically mean we want one global context only, not a separate one every time.

Interestingly,IPython (which works asynchronously under the hood too) only runs into part 2.

Thanks to the already mentionedGH-91150, this is very simple to manage and fix. Expect a PR soon.

CPython versions tested on:

CPython main branch

Operating systems tested on:

Linux

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.12only security fixes3.13bugs and security fixes3.14bugs and security fixestopic-asynciotopic-replRelated to the interactive shelltype-bugAn unexpected behavior, bug, or error

    Projects

    Status

    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions


      [8]ページ先頭

      ©2009-2025 Movatter.jp