Skip to content

UP038 rewrites code to make it slower and more verbose #7871

@AlexWaygood

Description

@AlexWaygood

(Apologies for the slightly negative tone in this issue -- I'm a big fan of ruff, and think it's a great tool!)


If I have a file foo.py, like so:

isinstance('foo', (int, bytes, dict, set, list, memoryview, str))

Running ruff foo.py --select=UP --fix --target-version="py310" on this file will make the following change:

-isinstance('foo', (int, bytes, dict, set, list, memoryview, str))
+isinstance('foo', int | bytes | dict | set | list | memoryview | str)

The new code is more verbose. I wouldn't mind that so much, however... if it wasn't also much slower!

>python -m timeit "isinstance('foo', (int, bytes, list, set, dict, str))"
Running PGUpdate|x64 interpreter...
2000000 loops, best of 5: 160 nsec per loop

>python -m timeit "isinstance('foo', int|bytes|list|set|dict|str)"
Running PGUpdate|x64 interpreter...
500000 loops, best of 5: 500 nsec per loop

(These timings are using a ~reasonably fresh PGO-optimised non-debug build of the CPython main branch on Windows.)

The rule can of course be switched off in a config file for ruff. But I'm not sure what advantages this rewrite actually has. The issue that proposed this rule (#2923) doesn't really give any motivations for wanting these isinstance() checks to be rewritten other than "it would be great". I also feel like the comment in #2923 (comment) is overly dismissive of the performance concerns when the writer says that "isinstance() checks... are very unlikely to dominate any real-world workloads". It can absolutely cause real-world issues if an isinstance() check is unexpectedly slow: see python/cpython#74690 (comment) as an example.

My preferred outcome would honestly be if ruff removed this rule. I feel like it's encouraging an anti-pattern, and it means that I can't have select = ["UP"] in my ruff config without also adding ignore = ["UP038"]. If it has to stay, though, I feel like the performance considerations should be loudly called out in the docs -- there's currently no mention of the potential issues here.

Note that this rule doesn't exist in pyupgrade itself, only in ruff: it was specifically rejected for pyupgrade, in part because of the performance issues: asottile/pyupgrade#736.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions