Repository navigation
Inspect module is prohibitively expensive to import #117865
Description
Activity
It looks like the bulk of the import time is spent in
re,ast, anddis:import time: self [us] | cumulative | imported package import time: 223 | 223 | types import time: 991 | 1214 | enum import time: 47 | 47 | _sre import time: 187 | 187 | re._constants import time: 270 | 456 | re._parser import time: 81 | 81 | re._casefix import time: 257 | 839 | re._compiler import time: 81 | 81 | itertools import time: 100 | 100 | keyword import time: 40 | 40 | _operator import time: 202 | 241 | operator import time: 131 | 131 | reprlib import time: 41 | 41 | _collections import time: 554 | 1146 | collections import time: 32 | 32 | _functools import time: 590 | 1766 | functools import time: 125 | 125 | copyreg import time: 384 | 4326 | re import time: 1325 | 1325 | _ast import time: 458 | 458 | contextlib import time: 861 | 6968 | ast import time: 281 | 281 | _opcode import time: 160 | 160 | _opcode_metadata import time: 220 | 660 | opcode import time: 594 | 1253 | dis import time: 129 | 129 | collections.abc import time: 116 | 116 | importlib import time: 43 | 158 | importlib.machinery import time: 141 | 141 | token import time: 20 | 20 | _tokenize import time: 569 | 729 | tokenize import time: 1548 | 10783 | inspectIf those imports could be deferred, that would likely address the concern.
- addedtype-featureA feature request or enhancementA feature request or enhancementperformancePerformance or resource usagePerformance or resource usage
on Apr 14, 2024 Visualised with https://github.com/nschloe/tuna:
./python.exe -X importtime -c 'import inspect' 2> /dev/null ; ./python.exe -X importtime -c 'import inspect' 2> import.log && tuna import.log

Some notes:
- The graph above isn't the whole story because many modules get imported multiple times. For example,
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).- I haven't checked if there are other paths by which
inspectimportsre. enumis imported byinspectandre. In both cases, it's for defining enums in the global scope, so it cannot easily be deferred.- It may also be worth running a line-profiling tool to see if any module is doing something especially expensive at import time. For example, it looks like
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?
Reacted by Alex Waygood, Hugo van Kemenade and Erlend E. Aasland- The graph above isn't the whole story because many modules get imported multiple times. For example,
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 :-)I think we can get rid of the
reimport, which would shave off about 1 ms.Reacted by Alex Waygood, Hugo van Kemenade, Sam Weber and Erlend E. Aasland- I haven't checked if there are other paths by which
inspectimportsre.
reis also used intokenize:- There are two
re.compiles which could be moved into a function with a localimport re - One
re.compilein a dedicated_compilefunction which looks unused and could be removed or given a localimport re - But one use in a global
Special = group(*map(re.escape, sorted(EXACT_TOKEN_TYPES, reverse=True)))which looks harder to refactor
- I haven't checked if there are other paths by which
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...)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.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
Benchmark 1: ./python -c "" Time (mean ± σ): 12.3 ms ± 0.6 ms [User: 10.0 ms, System: 2.5 ms] Range (min … max): 11.6 ms … 14.0 ms 25 runs Benchmark 2: ./python -c "import inspect" Time (mean ± σ): 32.1 ms ± 0.7 ms [User: 29.0 ms, System: 3.3 ms] Range (min … max): 30.6 ms … 33.6 ms 25 runs Benchmark 3: ./python -c "import dataclasses" Time (mean ± σ): 34.4 ms ± 1.5 ms [User: 30.2 ms, System: 4.3 ms] Range (min … max): 32.1 ms … 37.4 ms 25 runs Benchmark 4: ./python scratch/dataclass_with_docstring.py Time (mean ± σ): 34.3 ms ± 1.0 ms [User: 30.6 ms, System: 3.7 ms] Range (min … max): 32.7 ms … 36.5 ms 25 runs Benchmark 5: ./python scratch/dataclass_no_docstring.py Time (mean ± σ): 34.3 ms ± 1.0 ms [User: 30.5 ms, System: 4.0 ms] Range (min … max): 32.8 ms … 37.2 ms 25 runsResults from lazy_inspect
Benchmark 1: ./python -c "" Time (mean ± σ): 12.3 ms ± 0.8 ms [User: 10.5 ms, System: 2.2 ms] Range (min … max): 11.7 ms … 15.3 ms 25 runs Benchmark 2: ./python -c "import inspect" Time (mean ± σ): 21.6 ms ± 1.1 ms [User: 20.2 ms, System: 1.7 ms] Range (min … max): 20.6 ms … 24.1 ms 25 runs Benchmark 3: ./python -c "import dataclasses" Time (mean ± σ): 26.0 ms ± 1.1 ms [User: 22.8 ms, System: 3.5 ms] Range (min … max): 24.7 ms … 27.9 ms 25 runs Benchmark 4: ./python scratch/dataclass_with_docstring.py Time (mean ± σ): 26.7 ms ± 1.2 ms [User: 23.2 ms, System: 3.7 ms] Range (min … max): 25.2 ms … 29.3 ms 25 runs Benchmark 5: ./python scratch/dataclass_no_docstring.py Time (mean ± σ): 29.7 ms ± 2.2 ms [User: 26.8 ms, System: 3.1 ms] Range (min … max): 27.4 ms … 36.0 ms 25 runsFootnotes
- added a commit that references this issue
on Mar 2, 2026 I think this can now be closed.
inspectmodule is now faster to import (#144756 madereandtokenizelazy) and further improvements seem non-trivial.dataclasses is an unfortunately heavy import, yes. Although it imports relatively few modules itself, one of those modules is inspect, which has a huge import time (inspect imports half the standard library).
The main motivating case for this issue had been the
dataclassesmodule, but as of #137855 it no longer greedily imports inspect. 🎉It looks like the bulk of the import time is spent in re, ast, and dis:
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?)# Create constants for the compiler flags in Include/cpython/code.h # We try to get them from dis to avoid duplication mod_dict = globals() for k, v in dis.COMPILER_FLAG_NAMES.items(): mod_dict["CO_" + v] = k del k, v, mod_dict
Current tuna import graph from #144756

Metadata
Metadata
Assignees
Labels
Projects
- StatusShow more project fieldsNo status
From post by @AlexWaygood in #117372 (comment)_:
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):
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
inspectmodule #144756