Get started¶
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 (following PEP 810).
Error messages will mention __lazy_modules__, but the lazy keyword is
supported too.
Installation¶
Usually you would include this in some sort of dependency-group in your project,
e.g. dev or lint. There's also a standalone runner. If you use uv or pipx,
you can run it from anywhere without installation:
Run through flake8¶
The plugin is auto-discovered by flake8 via entry points.
Rule reference¶
1xx: Missing lazy declarations¶
| Code | Meaning |
|---|---|
LZY101 |
stdlib module should be listed in __lazy_modules__ |
LZY102 |
third-party or local module should be listed in __lazy_modules__ |
2xx: __lazy_modules__ validation¶
| Code | Meaning |
|---|---|
LZY201 |
__lazy_modules__ is not sorted |
LZY202 |
module listed in __lazy_modules__ is never imported |
LZY203 |
module listed in __lazy_modules__ is duplicated |
LZY204 |
__lazy_modules__ is assigned after importing modules it names |
LZY205 |
module listed in __lazy_modules__ must be an absolute name |
3xx: Native lazy keyword (Python 3.15+)¶
| Code | Meaning |
|---|---|
LZY301 |
lazy import inside suppress(ImportError) is misleading |
LZY302 |
module declared lazy by both lazy keyword and __lazy_modules__ |
LZY303 |
module imported both eagerly and lazily |
4xx: Lazy import safety and semantics¶
| Code | Meaning |
|---|---|
LZY401 |
module is declared lazy but accessed at the top level |
LZY402 |
module is an enclosing package for this file and should not be lazy |
Expected pattern¶
Declare a static, sorted list at module scope:
What is considered lazy-capable¶
flake8-lazy checks imports that execute at module scope and looks for runtime uses at module scope.
An import is considered lazy-capable when it is not needed immediately during module import.
The checker intentionally treats these as lazy-capable:
- Imports only referenced in annotations.
- Imports only referenced in
if typing.TYPE_CHECKING:guards. - Imports only used inside functions.
It intentionally ignores:
from __future__ import ...- Imports inside function and class bodies
For files inside a package, enclosing package names are also treated as
non-lazy. For example, in a/b/c.py, names a and a.b should not be declared
lazy (either in __lazy_modules__ or with lazy import).
Examples¶
Missing lazy module¶
__lazy_modules__ = ["argparse"]
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)
Diagnostic:
Unsorted list¶
Diagnostic:
Nested imports require exact name¶
Diagnostic:
Relative names are invalid in __lazy_modules__¶
Diagnostic:
Unused entry in __lazy_modules__¶
Diagnostic:
Duplicate entry in __lazy_modules__¶
Diagnostic:
Module accessed at module scope¶
Diagnostic:
Enclosing package listed as lazy¶
# file: a/b/c.py
__lazy_modules__ = ["a", "a.b", "requests"]
# Python 3.15+ equivalent
# lazy import a
# lazy import a.b
Diagnostics:
LZY402 module 'a' is an enclosing package for this file and should not be declared lazy
LZY402 module 'a.b' is an enclosing package for this file and should not be declared lazy
Lazy import inside suppress(ImportError) (Python 3.15+)¶
Diagnostic:
With a lazy import, the actual import happens at first use of the module, which
occurs outside the with suppress(ImportError): block. The suppression
therefore has no effect.
Redundant lazy and __lazy_modules__ declaration (Python 3.15+)¶
Diagnostic:
The lazy import keyword already makes the import lazy; listing the module in
__lazy_modules__ as well is redundant.
Module imported both eagerly and lazily (Python 3.15+)¶
Diagnostic:
CLI mode¶
You can also run the checker directly:
By default, output is flake8-like:
For a copy-pasteable recommendation, use the alternate format:
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, while keeping the same exit-status behavior.
To rewrite files in place with the recommended declaration, use --apply:
--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 1 if any diagnostics are produced.
Acknowledgements¶
GitHub Copilot in VS Code was used to help develop this package. The Scientific Python Development Guide template was used as a starting point.