GeezerVison is showing github.com — gh-137855: Lazy import `inspect` module in dataclasses by danielhollas · Pull Request #144387 · python/cpython
Conversation
danielhollas
commented
Feb 2, 2026
•
edited
Loading
Uh oh!
There was an error while loading. Please reload this page.
inspect module is slow to import (see #117865) and is dragging down dataclasses with it.
There are currently only two uses of inspect in dataclasses, but they are a bit tricky to inline since they are on a direct code path when the @dataclass decorator is executed.
inspect.signature is used to autogenerate class docstring (if one is not provided already)
inspect.unwrap is used in a rather esoteric code path only for slotted classes, added in gh-90562: Support zero argument super with dataclasses when slots=True #124455)
For 1. I have used a descriptor protocol to generate the __doc__ attribute on demand (this is my first time messing with descriptors, apologies if I overlooked something).
For 2. can be deferred by calling the unwrap functions only when really necessary (and hopefully this path is not common)
Benchmarks
./python -Ximporttime -c "import dataclasses"
Before
❯ hyperfine -w 10 './python -c "import dataclasses"'
Benchmark 1: ./python -c "import dataclasses"
Time (mean ± σ): 15.4 ms ± 1.8 ms [User: 11.6 ms, System: 3.6 ms]
Range (min … max): 13.1 ms … 20.2 ms 201 runs
After
❯ hyperfine -w 10 './python -c "import dataclasses"'
Benchmark 1: ./python -c "import dataclasses"
Time (mean ± σ): 11.0 ms ± 1.5 ms [User: 7.7 ms, System: 3.1 ms]
Range (min … max): 9.1 ms … 15.0 ms 271 runs
Overall seems to be a solid 20-30% improvement.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
|
|
|
|
# If this is a wrapped function, unwrap it.
|
|
|
member = inspect.unwrap(member)
|
|
|
if not isinstance(member, type) and hasattr(member, '__wrapped__'):
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This check was copied from the while loop in inspect.unwrap
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
Deferring the call to inspect.signature to create __doc__ might also speed up the dataclasses creation a bit. e.g. here's what I see in a cProfile of the _colorize module which uses dataclasses heavily (on main branch)
$ ./python -m cProfile -m _colorize | head 20
5604 function calls (5484 primitive calls) in 0.007 seconds
Ordered by: cumulative time
ncalls tottime percall cumtime percall filename:lineno(function)
9/1 0.004 0.000 0.007 0.007 {built-in method builtins.exec}
1 0.000 0.000 0.007 0.007 <string>:1(<module>)
1 0.000 0.000 0.007 0.007 <frozen runpy>:199(run_module)
1 0.000 0.000 0.007 0.007 <frozen runpy>:65(_run_code)
1 0.000 0.000 0.007 0.007 _colorize.py:1(<module>)
7 0.000 0.000 0.006 0.001 dataclasses.py:1432(wrap)
7 0.000 0.000 0.006 0.001 dataclasses.py:986(_process_class)
7 0.000 0.000 0.004 0.001 dataclasses.py:478(add_fns_to_class)
4 0.000 0.000 0.001 0.000 inspect.py:3343(signature)
4 0.000 0.000 0.001 0.000 inspect.py:3056(from_callable)
12/4 0.000 0.000 0.001 0.000 inspect.py:2437(_signature_from_callable)
4 0.000 0.000 0.001 0.000 inspect.py:2331(_signature_from_function)
71/11 0.000 0.000 0.000 0.000 annotationlib.py:907(get_annotations)
8 0.000 0.000 0.000 0.000 dataclasses.py:541(__annotate__)
17/9 0.000 0.000 0.000 0.000 annotationlib.py:1114(_get_and_call_annotate)
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
|
I believe we should add a NEWS entry, because it is user-facing change (at least in the performance terms).
|
danielhollas reacted with thumbs up emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
As a side effect that may be worth noting, deferring __doc__ generation also 'fixes' the output for self referential dataclasses1.
@dataclass
class Example:
examples: list[Example]
print(Example.__doc__)
Before:
"Example(examples: list[ForwardRef('Example', is_class=True, owner=<class '__main__.Example'>)])"
PR:
'Example(examples: list[__main__.Example])'
Every dataclass is getting its own _AutoDocString instance, could they share the same instance?
Following on from this, the re and copy imports could also potentially be deferred, copy is only used in some cases for the serialization methods and re is only used for string annotations.
Deferring re only makes sense if inspect is also deferred as inspect currently eagerly imports it (I see you have a PR to change that too though).
Footnotes
-
Arguably dataclasses should probably be doing something with Format.STRING here, other forward references are still ugly but that would be a separate issue. ↩
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
could they share the same instance?
They could; that would be nice.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Every dataclass is getting its own _AutoDocString instance, could they share the same instance?
Following on from this, the re and copy imports could also potentially be deferred, copy is only used in some cases for the serialization methods and re is only used for string annotations.
Done, thank you for the suggestions!
The lazy import or re is a bit more annoying since it is used to define a global compiled regex.
I am not sure I can judge whether the extra complexity is worth it --- the module will be imported as soon as you have e.g. from __future__ import annotations anywhere where dataclasses are defined.
Now the import time is dominated only by the "annotationlib" module
EDIT: Ugh, GitHub, you okay? The pushed new commits are still not appearing on the PR.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
I am not sure I can judge whether the extra complexity is worth it --- the module will be imported as soon as you have e.g. from __future__ import annotations anywhere where dataclasses are defined.
Ideally with the new annotations, uses of __future__ annotations will become less common and so they won't need the import. Modules like _colorize should already benefit as they don't use __future__ annotations.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
I'm not sure we should mix changes for _MODULE_IDENTIFIER_RE and __doc__ in one PR. But I'm not an expert here.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
@danielhollas If you open a new PR with the lazy imports and global regex I'm happy to merge those. I'd prefer someone else to review the autodoc stuff.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
@hugovk worth noting as it was one of the driving forces behind this that you lose a chunk of the benefit in _colorize without the autodoc stuff unless you write docstrings for the remaining dataclasses without them. The lack of docstring will trigger the inspect import.
One way of encouraging people to document their classes I guess 🙂 .
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
danielhollas
commented
Apr 2, 2026
•
edited
Loading
Uh oh!
There was an error while loading. Please reload this page.
@danielhollas If you open a new PR with the lazy imports and global regex I'm happy to merge those. I'd prefer someone else to review the autodoc stuff.
Thanks, I could do, but note that deferring re without deferring inspect is pointless since inspect imports re as well. (and it also wouldn't help _colorize since re is imported there as well). If you think splitting these two changes would help with the review I'll do it, but I think a more impactful thing here would be to tag somebody who you think should review this.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
This is a good improvement over main.
Here's importing _colorize on main vs. this PR (macOS optimised build):
|
| main
| PR
|
|
|
|
|
import _colorize
| 13 ms
| 7 ms
|
import dataclasses
| 6 ms
| 2 ms
|
Execute _colorize
| 6 ms
| 4 ms
|
|
danielhollas reacted with heart emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Thanks, I could do, but note that deferring re without deferring inspect is pointless since inspect imports re as well. (and it also wouldn't help _colorize since re is imported there as well).
I was wrong, I forgot that I made re lazy in inspect recently in #144756 😅 I've split the re and copy lazy imports in #148379. I will rebase this PR once that one is merged.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
Alright, #148379 has been merged (thanks Hugo!) so this PR is now only about lazy importing inspect module.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
|
After merging main, _colorize module no longer imports re. Here's a comparison on this PR:
versus main
|
hugovk reacted with hooray emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
Hi all, just a note that I will be away for two weeks starting 1st of May.
It would be great to get this merged before beta, since I don't think this would count as a "bugfix". While I am away, please feel free to push to this branch if any changes are needed.
|
hugovk reacted with rocket emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Reviewers
hugovk
hugovk approved these changes
ericvsmith
Awaiting requested review from ericvsmith
ericvsmith is a code owner
Uh oh!
There was an error while loading. Please reload this page.
Assignees
No one assigned
Milestone
No milestone
Development
Successfully merging this pull request may close these issues.
Uh oh!
There was an error while loading. Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
|