Inspect module is prohibitively expensive to import #117865
Description
From post by @AlexWaygood in #117372 (comment)_:
I'm a little dismayed that dataclasses adds so much overhead. Does that imply that dataclasses shouldn't be used in the stdlib?
dataclassesis an unfortunately heavy import, yes. Although it imports relatively few modules itself, one of those modules isinspect, which has a huge import time (inspectimports half the standard library). In the past, I've looked into removing thedataclassesdependency oninspect, but couldn't see a way to do so without making the code significantly more ugly; the same goes when it comes to improving the import time forinspectitself. (In hindsight, I'd argueinspectshould probably have been a package rather than a huge single-file Python module... but hindsight is 20/20.)
Perhaps inspect could be refactored to limit the import-time cost.
Currently on my m3 mac pro, on the second attempt, the import times are 1.2ms and 7.9ms (cumulative):
cpython main @ ./python.exe -X importtime -c 'import inspect' 2> /dev/null ; ./python.exe -X importtime -c 'import inspect' 2>1 | grep -E '(cumul|inspect)'
import time: self [us] | cumulative | imported package
import time: 1184 | 7892 | inspect
I've searched the issue tracker and didn't see anything tracking this concern, so right now it's a naïve feature request.
Related: #108901
Linked PRs
Metadata
Metadata
Assignees
Labels
Projects
- StatusShow more project fieldsDone
Activity
jaraco commentedon Apr 14, 2024
It looks like the bulk of the import time is spent in
re,ast, anddis:If those imports could be deferred, that would likely address the concern.
hugovk commentedon Apr 14, 2024
Visualised with https://github.com/nschloe/tuna:
JelleZijlstra commentedon Apr 14, 2024
Some notes:
reis imported both directly byinspectand also byast. Removing one of the two imports wouldn't actually help import time.astis used heavily in inspect.py, including as a base class. Deferring it would be difficult.reis used only once in inspect.py in a relatively obscure case (formatannotation); that import could be deferred.reis similarly only used once in ast.py and for a single function (https://docs.python.org/3.10/library/ast.html#ast.get_source_segment).inspectimportsre.enumis imported byinspectandre. In both cases, it's for defining enums in the global scope, so it cannot easily be deferred.astspends about 1 ms of import time on things outside importing other modules. What is that time being spent on, and can we improve it?AlexWaygood commentedon Apr 14, 2024
yeah, I looked at this in the past and decided that it wasn't realistically possible to improve this situation without either heavily refactoring
inspect.pyor making the code quite a lot uglier in order to defer imports. But I'd be very happy to be proved wrong if somebody can see a good way of making progress here :-)JelleZijlstra commentedon Apr 14, 2024
I think we can get rid of the
reimport, which would shave off about 1 ms.hugovk commentedon Apr 14, 2024
reis also used intokenize:re.compiles which could be moved into a function with a localimport rere.compilein a dedicated_compilefunction which looks unused and could be removed or given a localimport reSpecial = group(*map(re.escape, sorted(EXACT_TOKEN_TYPES, reverse=True)))which looks harder to refactorAlexWaygood commentedon Apr 14, 2024
A bunch of the
re-module usage intokenizeis backwards-compatibility stuff that isn't actually used bytokenize's core functionality anymore. We could easily deprecate that now, I think. (Previously discussed in #104719. Looks like I promised to open a followup issue there about deprecating the obsolete constants but never did so...)serhiy-storchaka commentedon Apr 16, 2024
The half of the
reimport cost is in importingenumandcollections. It is difficult to find a program that does not import them. Look not only at the cumulative time, but also at the pure time. The slowest are modules that create a large number of classes, especially with metaclasses.DavidCEllis commentedon Apr 19, 2024
Using a tool I have for lazy imports/exports I did some quick testing on deferring the imports for
ast,dis,reandtokenizeininspectto see what impact that has on bothinspectanddataclasses.This involved making
inspecta package and splitting out the definitions of_ClassFinderand the compiler flags into separate files to be lazily imported if needed. It didn't break./python -m test -v test_inspectat least (after making__main__.py).My machine isn't stable enough for really consistent testing but this gives a rough idea of the result.
Benchmarks 4 and 5 include actually creating a dataclass. The
dataclass_no_docstringdifference is because without a docstring dataclasses callsinspect.signatureto generate a basic docstring1 and this pulls in the code that usesdis2.Results from today's main
Results from lazy_inspect
Footnotes
Is this documented anywhere? ↩
This also means -OO will always pull in
dis. ↩pythongh-117865: Defer import of re in ast
gh-117865: Defer import of re in ast (#119546)
pythongh-117865: Defer import of re in ast (python#119546)
gh-117865: Speedup import of `inspect` module (#144756)
pythongh-117865: Speedup import of `inspect` module (python#144756)
danielhollas commentedon May 1, 2026
I think this can now be closed.
inspectmodule is now faster to import (#144756 madereandtokenizelazy) and further improvements seem non-trivial.The main motivating case for this issue had been the
dataclassesmodule, but as of #137855 it no longer greedily imports inspect. 🎉I looked into deferring
diswhich is the last heavy import besidesannotationlib, but unfortunately it is used to create a bunch of constants ininspectsglobal scope so I am not sure how to avoid it.(using module-level
__getattr__method would probably be a breaking change for start imports?)danielhollas commentedon May 1, 2026
Current tuna import graph from #144756