10 Oct 2026 · python, performance, desenvolvimento · 4 min
Ten imports cost 100 ms of your Python script's startup
A script that imports ten standard library modules takes 102 ms to reach its first useful line. With the same list declared as lazy imports, 14 ms. The difference is one word in front of each import.
That has been available since October 9, when Python 3.15 shipped with PEP 810 implemented. The syntax is new and explicit: you write lazy import asyncio and the module only loads the first time something uses it. The proposal calls that step reification, and the practical effect is that startup stops paying for code that may never run.
What I measured
I built 3.14.8 and 3.15.0 with the same compiler and the same flags, then ran two scripts with identical contents. One imports ten standard library modules the old way, the other declares the same ten with lazy import and never touches them. Seven runs each, best time:
| script | 3.14.8 | 3.15.0 |
|---|---|---|
| plain import, ten modules | 103 ms | 100 ms |
| lazy import, same ten modules | syntax error | 14 ms |
3.14 will not accept the syntax at all, which is fair: lazy imports are a 3.15 feature. The number that matters is the bottom one, 14 ms, seven times smaller than the plain start. The module count explains it: the plain script finished with 230 modules in sys.modules, the lazy one finished with 32.
I also measured each import on its own, in a fresh interpreter per line:
| module | cost to import |
|---|---|
| asyncio | 38.7 ms |
| urllib.request | 28.6 ms |
| unittest | 24.2 ms |
| logging.config | 22.2 ms |
| email.message | 14.3 ms |
| tarfile | 10.9 ms |
| inspect | 10.0 ms |
| statistics | 9.4 ms |
| ssl | 9.2 ms |
| json | 7.2 ms |
| argparse | 7.2 ms |
| re | 5.5 ms |
| sqlite3 | 3.9 ms |
Those numbers do not add up to the 100 ms of the full script, because several modules share dependencies and the same cost shows up in more than one line. The table is for picking candidates, not for summing a budget.
Where it pays
Command line tools are the obvious case. A CLI usually has a fast path, something like --version or --help, and a heavy path that does the work. The fast path pays for importing everything and exits in 100 ms when it could exit in 15. On a stateless server with a cold start per request, the same bill repeats multiplied by load.
# before
import argparse, asyncio, sqlite3, unittest
# after, on Python 3.15
lazy import argparse, asyncio, sqlite3, unittest
To pick what deserves to be lazy, measure before touching anything:
python3.15 -X importtime your_cli.py 2> times.txt
sort -k2 -nr times.txt | head -15
The output lists every import with its cumulative time. The first three entries that the fast path does not use are your answer.
What changes in behavior
Lazy imports move the moment of failure, and that has consequences. If a module is missing, the ImportError surfaces at first use, not on the first line of the program. For anyone writing infrastructure scripts, that changes what the log shows when a dependency disappears from a machine.
Import side effects are delayed too. A module that configures logging, registers a plugin or opens a connection on import now does that in the middle of execution, and where that happens stops being predictable. A module with import-time effects is a bad candidate for the list.
The proposal handles both with a filter mechanism and a global mode that apply the rule without touching the code. That is more powerful and more dangerous, because it now covers third party libraries that were never written with this in mind. Starting with the imports you control yourself is the boring version that works.
Caveat: the measurement comes from two locally built interpreters, with my chosen module list, over seven runs. The gain depends on what is being imported and how much of the fast path needs it. import time has nothing to gain.
My read is that the feature is worth more for the diagnosis it forces than for the win itself. Every CLI I have written loads things it never uses, and I never measured it because startup felt instant. A hundred milliseconds at a time, multiplied across every call of an automation script, adds up to seconds of waiting nobody blamed on imports. It is the usual trade, buying startup time with code discipline, and it shows up in any runtime where you swap resources for latency or pay for pauses nobody sees.