bare-imports
LintSurfaces a narrowly-used bare import that from x import … would replace.
An import that binds a name nothing references, and a second import that rebinds a name already bound, both add a line a reader parses for no information.
reaches either shape, dropping the unread member underdrop-unreferenced and the repeat under drop-duplicates, and it resolves both against the same binding table reads.A module repeats import os and names Any on a shared typing line that nothing reads. The repeat drops because the first import already ran the module, and Any drops off its line, leaving Final and the surviving os binding in place.
import os
from typing import Final
path: str = os.getcwd()
label: Final = "x"
The accounting runs per bound name rather than per line, so a member prunes off a shared from import while its siblings stay, and a line whose every member goes unread leaves with them.
Neither Any nor Final is referenced, so every alias on the typing line is pruned and the line goes rather than surviving empty.
value = 1
A repeat matches on the pair of names it carries, the one it binds and the qualified path it names at its source, so import os beside import os.path is two imports of two modules rather than one repeated.
A name a module publishes is meant to go unread inside it, and both facets read the same two markers. A name listed in __all__ holds its import, and so does the PEP 484 redundant-alias form from x import y as y, which is why a repeated self-alias survives drop-duplicates rather than dropping as a repeat.
The PEP 484 redundant-alias form declares loads part of the public surface. The rule holds the import even though nothing in the module reads it.
from json import loads as loads
value = 1
An __all__ built from anything other than a list or tuple of string literals leaves the public surface unsettled, and every import in that module holds, as does one written anywhere below module scope. A from … import * holds too, binding a name set no reference count enumerates.
Two other reads keep an import the reference count alone would call unused. A del of the bound name needs the binding to exist, so removing the import would leave the statement raising NameError. And a name read only inside a quoted annotation sits in a string literal rather than in the tree the binding table walks, so the rule parses each quoted annotation for the names it reads and holds their imports, following the nesting where one quoted member encloses another.
List is read only inside a string-literal annotation, where the binding table cannot see it. The import holds, the rule parsing each quoted annotation for the names it reads.
from typing import List
x: "List[int]" = []
An import binding __all__ itself sets the whole export surface in one line, so it holds on the same ground a listed name does. A name a second import rebinds holds as well, because the module-scope binding then carries more than one write, and the extension shim that pairs try: from _speedups import loads with a pure-Python import above it needs that earlier binding on the branch the ImportError takes.
An own-line comment sitting directly above an import holds the whole statement too. Dropping the line would strand the comment on whatever statement follows, where it reads as a description of code it was never written about, and a comment in that position is often the record of why the import is load-bearing despite binding a name nothing reads.
Nothing reads multiprocessing, and the import is loaded for its side effect alone. The line holds, because deleting it would strand the comment on whatever statement follows.
# Imported so the submodule loads and mp.connection resolves later
import multiprocessing.connection
A package __init__.py is the one file where an unreferenced import is reported rather than dropped, since the names it binds are the package's re-export surface and no single-file pass settles whether the package itself is what reads them. A repeat still drops there, resolving out of sys.modules without running its module again.
__future__ Directive from __future__ import annotations drops on any of three branches, when the module carries no annotation at all, when target-version is 3.14 or higher and PEP 749 defers annotation evaluation, or when every name every annotation reads resolves to an unconditional module-scope binding written before it.
Every other __future__ feature stays. A directive such as division changes how the module compiles rather than binding a name a reference count can settle.
A from __future__ import division may still carry semantic weight on legacy code paths and is out of scope. The rule fires only on the annotations alias, leaving every other __future__ directive untouched.
from __future__ import division
x = 1 / 2
The version-gated branch stays quiet, so the directive goes only where the module carries no annotation or every annotation resolves against an earlier module-scope binding.
| Key | Type | Default | Meaning |
|---|---|---|---|
enabled | bool | true | Toggles the rule on or off. |
drop-duplicates | bool | true | Drops an import rebinding a name an earlier import already bound to the same source. false keeps every repeat. |
drop-unreferenced | bool | true | Drops an import binding a name nothing references, where the binding is not marked for re-export, read by a del or a quoted annotation, or bound in a package __init__.py, where it is reported instead. false leaves every unreferenced import in place and reports none of them. |
The target-version field from the top-level Configuration gates the __future__ branch per project.
A module repeats import os and names Any on a shared typing line that nothing reads. The repeat drops because the first import already ran the module, and Any drops off its line, leaving Final and the surviving os binding in place.
import os
from typing import Final
path: str = os.getcwd()
label: Final = "x"
import os, sys binds one module per name and nothing reads either. Both aliases go and the line with them.
Neither Any nor Final is referenced, so every alias on the typing line is pruned and the line goes rather than surviving empty.
__all__ publishes dumps and says nothing of loads, and neither is read inside the module. The listed name holds its slot on the line while the unlisted one prunes off it.
Nothing references json, and its line binds nothing else. The whole line goes, leaving the blank that followed it as a leading blank.
The annotations alias sits last in a multi-name from __future__ import division, annotations. The surgical deletion strips the preceding comma alongside the alias, leaving the earlier division entry's formatting intact.
A from __future__ import annotations as legacy still imports the annotations feature regardless of the local binding name. The rule recognizes the directive through the as form and deletes the line.
import numpy as np binds np rather than numpy, and it is np the reference count reads. Nothing references it, so the line goes.
Nothing reads the json namespace, and the from json import loads line keeps the module imported. The bare import drops rather than being reported.
The annotations alias leads a multi-name from __future__ import annotations, division. Only the annotations entry is removed, taking its separator comma with it, leaving division as the sole import.
A # fmt: off block wrapping the directive keeps it in place. The pipeline drops any edit whose range overlaps a suppressed span, so suppression is handled centrally rather than re-implemented per rule.
The PEP 484 redundant-alias form declares loads part of the public surface. The rule holds the import even though nothing in the module reads it.
Nothing reads multiprocessing, and the import is loaded for its side effect alone. The line holds, because deleting it would strand the comment on whatever statement follows.
drop-duplicates reads the same re-export guard as drop-unreferenced, so a repeated import of a name __all__ publishes stays rather than dropping as a repeat.
Nothing reads os, and a del os still needs the binding to exist. The import holds, since dropping it would leave the del raising NameError.
loads goes unread inside the module while __all__ publishes it. The re-export guard holds the import and draws no report.
List is read only inside a string-literal annotation, where the binding table cannot see it. The import holds, the rule parsing each quoted annotation for the names it reads.
The extension shim rebinds loads inside the try, so the module-scope binding carries two writes. The first import holds, since the ImportError branch falls back to it.
Nothing references json, and a # prose: skip directive sits on its line. The prune is dropped as a whole group, leaving the import exactly as written.
from os.path import * binds a name set the reference count cannot enumerate. The line holds and draws neither a prune nor a report.
__all__ is written inside an if rather than at module scope, so the public surface cannot be settled. Every import holds, the same as under an __all__ built by a call.
Nothing reads __all__ inside the module, and the import is what sets the whole export surface. The alias holds, on the same ground a name a written __all__ lists does.
A module writes __all__ twice, and the rule reads the union rather than the last write, so both dumps and loads hold their import even though only the second list publishes at runtime.
Surfaces a narrowly-used bare import that from x import … would replace.
Partitions a module's imports into __future__, bare, external from, and local-package sections.
Rewrites Optional[T], Union[X, Y], and the typing generics to the T | None, X | Y, and builtin forms the target runtime carries.
Surfaces single-use local bindings that could inline cleanly.
For the gate semantics, target-version in the Configuration chapter covers how the field is read across version-gated rules.