flake8-lazy is a flake8 plugin that finds imports which can be made lazy in Python 3.15 (following PEP 810). See the post here for more on the development of this tool!
flake8-lazy helps keep import-time overhead low by detecting imports that can be
declared as lazy in __lazy_modules__. For this package itself,
flake8-lazy --help runs roughly twice as fast when using Python 3.15's new
lazy import system.
Error messages will mention __lazy_modules__ since that is backward compatible
with older Python versions, but the lazy keyword is supported too.
There's a standalone flake8-lazy runner. If you use uv or pipx, you can run it
from anywhere without installation:
uvx flake8-lazy <filenames>
# OR
pipx run flake8-lazy <filenames>Try --format=lazy-modules to get copy-paste lines or even --apply to have
the tool update your lazy modules automatically!
python -m pip install flake8-lazyUsually you would include this in some sort of dependency-group in your project,
e.g. dev or lint.
flake8 will automatically discover the plugin.
LZY101: Missing lazy stdlib module in__lazy_modules__LZY102: Missing lazy third-party or local module in__lazy_modules__
LZY201:__lazy_modules__list is not sortedLZY202: Module listed in__lazy_modules__is never importedLZY203: Module listed in__lazy_modules__appears more than onceLZY204:__lazy_modules__is assigned after importing modules it namesLZY205: Module name in__lazy_modules__is relative (.name) instead of absolute
LZY301: Lazy import insidesuppress(ImportError)is misleadingLZY302: Module is declared lazy by bothlazykeyword and__lazy_modules__LZY303: Module is imported both eagerly and lazily
LZY401: Module is declared lazy but accessed at the top levelLZY402: Module is an enclosing package for this file and should not be lazy
__lazy_modules__ = ["argparse", "requests"]
import argparse
import requests
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("url")
args = parser.parse_args()
response = requests.get(args.url, timeout=5)
print(response.status_code)In this example, requests is only used inside main, so it can be lazy. The
checker expects it in __lazy_modules__ and emits LZY102 until you add it.
Running --help will not import requests, resulting in a more responsive app!
flake8-lazy inspects module-scope imports and module runtime usage.
- Counts top-level
importandfrom ... import ...statements. - Currently treats annotation-only usage as lazy-capable
(
from __future__ import annotationsif not using 3.14+). - Treats usage inside
if typing.TYPE_CHECKING:as type-only. - Handles static
sys.version_infochecks - Skips
from __future__ import .... - Requires exact module entries for nested imports.
- Treats enclosing package names as non-lazy for a file. For example, in
a/b/c.py,aanda.bshould not be listed as lazy.
Nested import note:
import email.header
__lazy_modules__ = ["email"] # Not enoughThis emits LZY101; the required entry is "email.header". PEP 810 requires
full module names.
Missing relative imports use f"{__spec__.parent}.name".
Enclosing package note:
# file: a/b/c.py
__lazy_modules__ = ["a", "a.b", "requests"]
# Python 3.15+ also applies
# lazy import a
# lazy import a.bThis emits LZY402 for a and a.b. Those are enclosing packages for the
current file, so declaring them lazy is unnecessary and can be removed.
The project also provides a direct CLI runner:
flake8-lazy path/to/file.py another_file.py
# or
uvx flake8-lazy path/to/file.py another_file.pyThe default output format matches flake8-style diagnostics:
path/to/file.py:12:0: LZY102 module 'numpy' should be listed in __lazy_modules__
You can also ask for a copy-pasteable recommendation instead:
flake8-lazy --format lazy-modules path/to/file.pypath/to/file.py: __lazy_modules__ = ["numpy", "pandas"]
This prints the sorted __lazy_modules__ value the checker recommends for each
file when it differs from the file's current static __lazy_modules__
declaration. The command still exits with status code 1 if the file has any
diagnostics.
To rewrite files in place with the recommended declaration, use --apply:
flake8-lazy --apply path/to/file.py another_file.py--apply replaces an existing top-level __lazy_modules__ assignment when
present. If there is no assignment yet, one is inserted near the top of the file
after leading comments/docstrings (and after from __future__ import ... lines,
to keep valid Python syntax).
The command exits with status code 1 if any error is found.
Use a static, sorted list of strings:
__lazy_modules__ = [
"argparse",
"numpy",
"pathlib",
]Dynamic values are intentionally ignored for now.
Run tests:
nox -s tests
# or
uv run pytestRun linting:
nox -s lint
# or
prek -aBuild docs:
nox -s docs --non-interactiveServe docs locally:
nox -s docsBump the version:
uv version <new_version>It's really new, and it's a bit complex. Maybe someday it will be? :)
I don't like flake8's config file choices, so I've tried to make it work out of the box. Probably will get some eventually, though, like a list of modules that can't be lazy imported, or a 'simple' mode that requires every module to be lazy.
Open an issue! If it's clear and detailed and in-scope, I might even be able to assign it to copilot!
GitHub Copilot in VS Code was used to help develop this package. The Scientific Python Development Guide template was used as a starting point.