Skip to content

reassigned-constants

reassigned-constants reports a module-level SCREAMING_CASE binding the module reassigns, because the casing promises a constant and a second write contradicts it. A binding counts as reassigned when the binding table records more than one write against the name or an augmented assignment, and a write-once constant stays silent whatever its value. The fix is to rename the variable to lowercase or to stop reassigning it, and the lint leaves that work to a later migration pass that reads its output.

The rule reads module-level SCREAMING_CASE assignments and annotated assignments and reports only the reassigned ones, whereas several kinds of binding stay quiet:

  • A name on the configurable allow list.
  • A dunder name (__version__, __all__), which falls outside SCREAMING_CASE because it leads with an underscore.
  • A typing construct from the standard library (TypeVar, ParamSpec, NewType, TypeAliasType) and any binding declared inside an if TYPE_CHECKING: block, because both carry semantics of their own distinct from runtime configuration.
  • In-place mutation through a method call or a subscript store, which the binding table records as a read, so it stays out of scope.

The lint never rewrites, so the diagnostic is reported and the source stays as written.

Configuration

KeyTypeDefaultMeaning
enabledbooltrueTurns the rule on or off.
allowlist of names[]Module-level names exempted from the lint.

The allow list takes bare names, and a listed name never produces a finding even when it would otherwise match.

The Canonical Case

RETRIES = 3 is followed by RETRIES = 5, a second write to a SCREAMING_CASE name at module scope. Both lines are reported, because the casing promises a write-once constant and the second write contradicts it, whereas a constant written exactly once produces no finding.

RETRIES = 3
RETRIES = 5
python

More Examples

LOG_LEVEL and CACHE_DIR are each assigned twice at module level, and the case runs with allow = ["LOG_LEVEL"]. Both CACHE_DIR lines are reported and neither LOG_LEVEL line is, one diagnostic per write, because a name on the allow list never produces a finding.

FLAG: bool declares a name without a value and MAX: int = 1 declares one with a value, and the plain FLAG = True and MAX = 2 then rebind each. All four lines are reported, because an annotated assignment counts toward the write count whether or not a value follows the annotation.

No Change

RETRIES = 3 and RETRIES = 5 sit between # fmt: off and # fmt: on. No diagnostic is reported, because a block marker suppresses lint diagnostics as well as rewrites, whereas a line-level # prose: skip reaches rewrites only.

No Change

DEFAULT_BACKEND = "memory" is assigned inside if typing.TYPE_CHECKING:, with the guard spelled through the module attribute rather than the bare name. No diagnostic is reported, because the rule matches the dotted form as well as the bare one, so the block is skipped and the write inside it never counts as a module-scope write.

No Change

__version__ is assigned once, and main defines no module-level constant. No diagnostic is reported and the file passes through unchanged, because reassigned-constants is a lint that never rewrites source, whether or not it finds anything.

No Change

T is written twice at module scope, as TypeVar("T") and again as TypeVar("T", bound=int), so the write count alone would report it. No diagnostic is reported, because each write binds a TypeVar call, the typing construct the rule exempts.

No Change

PI = 3.14 and MAX_RETRIES = 5 are each assigned exactly once. No diagnostic is reported for either, because the rule counts writes rather than reading values, so a float and an int pass the same way.

No Change

A = B = 1 chains two targets, A, B = 1, 2 unpacks a tuple, and FOO.bar = 1 assigns through an attribute, so A and B are each written twice across the three lines. No diagnostic is reported for any line, because the rule tracks assignments to a single bare name only.

No Change

__version__, __all__, and __author__ are each assigned once, and each leads with an underscore. No diagnostic is reported for any of them, because a dunder falls outside the SCREAMING_CASE pattern the rule matches, and the runtime writes those names itself.

For per-line opt-outs, the Suppression chapter covers the # prose: ignore[reassigned-constants] directive.