single-letter-name (SMP101)¶
Flags single-letter names outside comprehensions.
Why is this bad?¶
A single letter says nothing about what the name holds, so the reader has to find where it is bound to learn it. Comprehensions are exempt because the iterable the names come from sits right next to them.
Example¶
1def scale(v: float, f: float) -> float:
2 r = v * f
3 return round(r, 2)
1def scale(value: float, factor: float) -> float:
2 scaled = value * factor
3 return round(scaled, 2)
What it does¶
No fix is offered, since a meaningful name cannot be derived from the code.
Active on¶
Functions and classes: their names show up at every call site, far from the definition that explains them.
Example: reported
1def f() -> int: 2 return 1
Parameters: the body reads them away from the signature, and callers see the name in the signature and keyword arguments. This covers:
Parameters of a
def, such asvindef scale(v: float):Example: reported
1def scale(v: float) -> float: 2 return v * 2
Parameters of a lambda, such as
vinlambda v: v:Example: reported
1callback = lambda v: v
Assignment and walrus targets: the value is computed once, and every later use relies on the name alone to say what it is.
=assignments, such asr = 3:Example: reported
1r = 3 2print(r)
:=walrus targets, such asninif (n := 3) > 2:Example: reported
1if (n := 3) > 2: 2 print(n)
with ... as,except ... asandimport ... asnames: the alias replaces the expression, exception or module name that said what the value is.with ... asnames, such asfinwith open("data.txt") as f:Example: reported
1with open("data.txt") as f: 2 print(f.read())
except ... asnames, such aseinexcept ValueError as e:Example: reported
1try: 2 run() 3except ValueError as e: 4 print(e)
import ... asnames, such asoinimport os as o:Example: reported
1import os as o
matchcaptures: the case body reads the captured value under that name.Example: reported
pyproject.toml1[project] 2requires-python = ">=3.12"
1value = 1 2match value: 3 case x: 4 print(x)
Mapping rest captures:
**restin a mapping pattern binds the remaining keys, and the case body reads them under that name.Example: reported
1value = {"a": 1, "b": 2} 2match value: 3 case {"a": 1, **r}: 4 print(r)
Type parameters such as
T: they appear throughout the signature and body of the generic definition.Example: reported
pyproject.toml1[project] 2requires-python = ">=3.12"
1def first[T](items: list[T]) -> T: 2 return items[0]
forloop targets, such asiinfor i in range(3): a loop body can run for many lines, far from theforline that says what the name holds.Example: reported
1for i in range(3): 2 print(i)
Exceptions¶
Names inside a comprehension: every name inside a list, set or dictionary comprehension or a generator expression, including a walrus target or lambda parameter, so
[x * 2 for x in values]is fine. The whole expression keeps the iterable next to every use of the name.Example: not reported
1values = [1, 2] 2doubled = [x * 2 for x in values]
_: the conventional throwaway name, which says the value is unused.Example: not reported
1for _ in range(3): 2 pass
Parameters of a method decorated with
@override: the method must keep the parameter names of the method it replaces.Example: not reported
pyproject.toml1[project] 2requires-python = ">=3.12"
1from typing import override 2 3 4class Base: 5 def run(self, value: int) -> None: 6 pass 7 8 9class Child(Base): 10 @override 11 def run(self, v: int) -> None: 12 pass
l,IandO: Ruff’s ambiguous-variable-name, ambiguous-class-name and ambiguous-function-name report these letters wherever this rule would, so this rule leaves them to Ruff while the project’s Ruff configuration selects the rule for the kind of binding. Ruff’s own suppressions of it, throughper-file-ignoresornoqa, are respected too.Example: not reported
pyproject.toml1[tool.ruff.lint] 2select = ["E741"]
1l = 3
Reporting¶
Each name is reported once per scope, at its first binding.
Ruff overlap¶
Ruff’s E741, E742 and E743 report l, I and O on variables, parameters, except names, classes and functions, with
no fix. This rule leaves those three letters to Ruff at those sites, and only while the project’s Ruff configuration
selects the rule of each kind. Ruff’s own suppressions of it, through per-file-ignores or noqa, are respected too.
It keeps every other single letter, and the l, I and O that Ruff does not check: import aliases, match captures
and type parameters.
Other tools¶
Ruff: variation of released rules E741, E742 and E743, which report
l,IandOand which this rule leaves to them; issue #4866 asking for all single letters was closed as not planned.flake8-variables-names reports single-letter names as VNE001, but allows
i,_andTby default. This rule flagsfor iloop targets and type parameters such asT.wemake-python-styleguide reports short names as WPS111, with a configurable minimum length. It also checks module and attribute names, which this rule does not list.