Skip to content

Specialize contextlib.contextmanager #905

Open
python/mypy
#22267

Description

@JukkaL

contextlib.contextmanager is quite popular, and using it currently involves executing slow Python method calls in with statements. We could specialize this in mypyc for decent performance gains.

I also experimented with manually rewriting some context managers in mypy to not rely on contextlib.contextmanager, but this was actually quite tedious and makes code harder to maintain, so it's not a great alternative.

Depends on #904.

Activity

  1. ilevkivskyi commented on Jan 22, 2026

    @ilevkivskyi
    Collaborator

    Now that mypy is faster this can bring potentially big perf win for mypy, so I raised priority to high.

  2. JukkaL commented on Jan 22, 2026

    @JukkaL
    CollaboratorAuthor

    Since general context managers are pretty hard to optimize, we could instead add librt.contextlib.contextmanager that only works for sufficiently simple context managers.

    First, it should be easy to support context managers with a single yield statement at the top level block (generate a class with suitable __enter__ and __exit__ methods):

    @contextmanger
    def foo(...) -> ...:
         ...
         yield
         ...

    Second, yield in try/finally is also common:

    @contextmanager
    def foo(...) -> ...:
        ...
        try:
            yield
        finally:
            ...

    Again, we should be able to compile this into a class with __enter__ and __exit__.

    The third most common use case seems to be yield within a with statement. This might not be too hard either, but I'm not sure.

  3. JukkaL commented on Jan 22, 2026

    @JukkaL
    CollaboratorAuthor

    Or maybe I'm overthinking this -- maybe it's sufficient to compile a normal generator and call the generator methods directly when the context manager is used.

  4. ilevkivskyi commented on Jan 22, 2026

    @ilevkivskyi
    Collaborator

    @JukkaL Yes, I think simply avoiding a bunch of slow Python calls should be enough (although I didn't do any profiling I intuitively feel this is where the problem occurs). I don't think we will need to look into the generator body, just make sure all calls (i.e. with ... -> .send() etc) are fast C-level calls.

  5. ilevkivskyi commented on Jan 22, 2026

    @ilevkivskyi
    Collaborator

    That said, if we are going this way, it looks like we will need to replicate all the tedious dances with exceptions in __exit__() from here https://github.com/python/cpython/blob/main/Lib/contextlib.py#L145

  6. hauntsaninja commented on Jan 27, 2026

    @hauntsaninja
    Collaborator

    I took a shot at it, but (assuming I did it correctly) it's slower than the original... I think exception handling is slow, looks like #476 is related

  7. ilevkivskyi commented on Jan 29, 2026

    @ilevkivskyi
    Collaborator

    I took a shot at it, but (assuming I did it correctly) it's slower than the original...

    TBH I am surprised. We have special handling for StopIteration (normally we don't raise it and instead return a special value, stop_iter_ptr). And any other exceptions should happen very rarely. I guess it would make sense to look into what you did in details.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions