Skip to content

[mypyc] Specialize isinstance() with mixed and nested tuples - #22098

Open
rheard wants to merge 2 commits into
python:masterfrom
rheard:fix-mypyc-1227
Open

rheard wants to merge 2 commits into
python:masterfrom
rheard:fix-mypyc-1227

Conversation

@rheard

@rheard rheard commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

Fixes mypyc/mypyc#1227

This is just an idea for how to improve performance, for an issue that was exposed to me because of the issues in PR #22097 .

isinstance(x, (A, B)) is specialized when every class in the tuple is a primitive type (#19949) or every class is a native class (mypyc/mypyc#413). Anything else falls back to the generic path, which builds a new tuple and calls PyObject_IsInstance on every call, so the primitive types in the tuple lose their fast checks as well. That covers:

  • a primitive type mixed with a native class: isinstance(x, (float, MyNativeClass))
  • a primitive type mixed with any other class: isinstance(x, (float, Decimal))
  • nested tuples, even when every class is a primitive type: isinstance(x, (int, (float, str)))

The chained checks added in #19949 could be generalized: flatten nested tuples, use the fast checks for primitive types and native classes, and check any other class with its own PyObject_IsInstance call, so no tuple is built. In a quick microbenchmark this takes isinstance(x, (float, MyNativeClass)) from about 50 ns to about 5 ns per call.

Relatedly, isinstance(f(), ()) currently returns False without calling f().

@ilevkivskyi

Copy link
Copy Markdown
Member

Thanks for all the mypyc PRs! I will probably leave them all to @JukkaL and @p-sawicki (or maybe I will take one-two if they are busy).

@JukkaL

JukkaL commented Oct 5, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the! This is a useful improvement. I found a few regressions when trying this out. First, this crashes the compiler:

def f(x: object, classes: tuple[type, ...]) -> bool:
    return isinstance(x, (int, *classes))

The assertion here unexpectedly fails (this is pretty esoteric though):

def f(x: object) -> bool:
    cls: type = int
    return isinstance(x, (cls, (cls := str)))

assert f(1)

Maybe fall back to the old approach in these cases? However, this could slightly change semantics.

@rheard

rheard commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor Author

@JukkaL Oof. Good catch. I'll be honest, out of all the mypyc PRs I opened this weekend, this one was the one I had the least confidence in. Fwiw, #22099 is second...

You are correct, I think the only way to handle those is to fall back to the old method since a star-expansion can have a variable number of items of unreliable types, and a walrus operator can change the values. I think the walrus regression would be the mypyc equivalent of #21249 . I've also added tests for this.

Also just testing now I think I found an issue that also fails on master which is similar to the second regression. I don't see it reported anywhere. Your test there is about when the walrus replaces a var in the tuple, but I seem to have problems when it replaces another arg as well. For example, pair(i, (i := 1)) passes (1, 1) instead of (0, 1) on master.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

isinstance() with a mixed or nested tuple builds a new tuple on every call

3 participants