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=list to
have the tool update your lazy modules automatically! For maximum laziness, try
--apply=dynamic.
The standalone runner also reads defaults from a [tool.flake8-lazy.standalone]
table in your pyproject.toml; see the
CLI docs for details.
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.
flake8-lazy ships a pre-commit /
prek hook. Add it to your .pre-commit-config.yaml:
- repo: https://github.com/henryiii/flake8-lazy
rev: v0.8.1
hooks:
- id: flake8-lazyThe hook reports diagnostics by default; add args: [--apply=list] (or another
--apply mode) to have it rewrite files in place on commit. This is the
built-in runner, use flake8's pre-commit integration if you want flake8 to run
it.
See the full documentation for details, examples, and the standalone CLI runner.
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!
Use a static, sorted list of strings:
__lazy_modules__ = [
"argparse",
"numpy",
"pathlib",
]You can also use sets; sets are slower to construct, but faster to check - around 2 items is enough to make sets faster.
Dynamic values (i.e. a custom object assigned to __lazy_modules__) are also
supported. If flake8-lazy detects a non-static assignment it treats the file as
fully covered and suppresses all LZY1xx/LZY2xx diagnostics. Use
--apply=dynamic to have the tool write a simple catch-all object:
class AllLazy:
@staticmethod
def __contains__(_: str) -> bool:
return True
__lazy_modules__ = AllLazy()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.
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:
$ flake8-lazy path/to/file.py
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.py
path/to/file.py: __lazy_modules__ = ["numpy", "pandas"]To rewrite files in place with the recommended declaration, use --apply:
flake8-lazy --apply=list path/to/file.py another_file.pyAvailable modes: list, set, native (3.15+ syntax), dynamic. Long list
and set assignments are split one module per line with a trailing comma
(black/ruff style) when they exceed --line-length (default 88), so the
output needs no reformatting. The command exits with status code 1 if any
error is found.
See CONTRIBUTING.md for local development instructions.
It's really new, and it's a bit complex. Maybe someday it will be? :)
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.