<a id="smp101"></a>

<a id="single-letter-name"></a>

# single-letter-name (SMP101)

<div class="badges"><span class="badge code">SMP101</span><span class="badge fix-none">No fix</span><span class="badge">Naming</span><a class="badge" href="../../rules.html">All rules</a></div>

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

```python
def scale(v: float, f: float) -> float:
    r = v * f
    return round(r, 2)
```

```python
def scale(value: float, factor: float) -> float:
    scaled = value * factor
    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

  ```python
  def f() -> int:
      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 as `v` in `def scale(v: float)`:

    ### Example: reported

    ```python
    def scale(v: float) -> float:
        return v * 2
    ```
  - Parameters of a [lambda](https://docs.python.org/3/reference/expressions.html#lambda), such as `v` in `lambda v: v`:

    ### Example: reported

    ```python
    callback = lambda v: v
    ```
- **Assignment and [walrus](https://docs.python.org/3/reference/expressions.html#assignment-expressions) targets**: the
  value is computed once, and every later use relies on the name alone to say what it is.
  - `=` assignments, such as `r = 3`:

    ### Example: reported

    ```python
    r = 3
    print(r)
    ```
  - `:=` walrus targets, such as `n` in `if (n := 3) > 2`:

    ### Example: reported

    ```python
    if (n := 3) > 2:
        print(n)
    ```
- **`with ... as`, `except ... as` and `import ... as` names**: the alias replaces the expression, exception or module
  name that said what the value is.
  - `with ... as` names, such as `f` in `with open("data.txt") as f`:

    ### Example: reported

    ```python
    with open("data.txt") as f:
        print(f.read())
    ```
  - `except ... as` names, such as `e` in `except ValueError as e`:

    ### Example: reported

    ```python
    try:
        run()
    except ValueError as e:
        print(e)
    ```
  - `import ... as` names, such as `o` in `import os as o`:

    ### Example: reported

    ```python
    import os as o
    ```
- **[`match`](https://docs.python.org/3/reference/compound_stmts.html#the-match-statement) captures**: the case body
  reads the captured value under that name.

  ### Example: reported

  `pyproject.toml`
  ```toml
  [project]
  requires-python = ">=3.12"
  ```

  ```python
  value = 1
  match value:
      case x:
          print(x)
  ```
- **Mapping rest captures**: `**rest` in a mapping pattern binds the remaining keys, and the case body reads them under
  that name.

  ### Example: reported

  ```python
  value = {"a": 1, "b": 2}
  match value:
      case {"a": 1, **r}:
          print(r)
  ```
- **[Type parameters](https://docs.python.org/3/reference/compound_stmts.html#type-params)** such as `T`: they appear
  throughout the signature and body of the generic definition.

  ### Example: reported

  `pyproject.toml`
  ```toml
  [project]
  requires-python = ">=3.12"
  ```

  ```python
  def first[T](items: list[T]) -> T:
      return items[0]
  ```
- **`for` loop targets**, such as `i` in `for i in range(3)`: a loop body can run for many lines, far from the `for`
  line that says what the name holds.

  ### Example: reported

  ```python
  for i in range(3):
      print(i)
  ```

### Exceptions

- **Names inside a comprehension**: every name inside a list, set or dictionary
  [comprehension](https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions) or a
  [generator expression](https://docs.python.org/3/reference/expressions.html#generator-expressions), 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

  ```python
  values = [1, 2]
  doubled = [x * 2 for x in values]
  ```
- **`_`**: the conventional throwaway name, which says the value is unused.

  ### Example: not reported

  ```python
  for _ in range(3):
      pass
  ```
- **Parameters of a method decorated with
  [`@override`](https://docs.python.org/3/library/typing.html#typing.override)**: the method must keep the parameter
  names of the method it replaces.

  ### Example: not reported

  `pyproject.toml`
  ```toml
  [project]
  requires-python = ">=3.12"
  ```

  ```python
  from typing import override


  class Base:
      def run(self, value: int) -> None:
          pass


  class Child(Base):
      @override
      def run(self, v: int) -> None:
          pass
  ```
- **`l`, `I` and `O`**: Ruff’s [ambiguous-variable-name](https://docs.astral.sh/ruff/rules/ambiguous-variable-name/),
  [ambiguous-class-name](https://docs.astral.sh/ruff/rules/ambiguous-class-name/) and
  [ambiguous-function-name](https://docs.astral.sh/ruff/rules/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, through `per-file-ignores` or `noqa`, are respected too.

  ### Example: not reported

  `pyproject.toml`
  ```toml
  [tool.ruff.lint]
  select = ["E741"]
  ```

  ```python
  l = 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](https://github.com/astral-sh/ruff):** variation of released rules
  [E741](https://docs.astral.sh/ruff/rules/ambiguous-variable-name/),
  [E742](https://docs.astral.sh/ruff/rules/ambiguous-class-name/) and
  [E743](https://docs.astral.sh/ruff/rules/ambiguous-function-name/), which report `l`, `I` and `O` and which this rule
  leaves to them; issue [#4866](https://github.com/astral-sh/ruff/issues/4866) asking for all single letters was closed
  as not planned.
- [flake8-variables-names](https://github.com/best-doctor/flake8-variables-names) reports single-letter names as
  [VNE001](https://github.com/best-doctor/flake8-variables-names/blob/master/flake8_variables_names/checker.py), but
  allows `i`, `_` and `T` by default. This rule flags `for i` loop targets and type parameters such as `T`.
- [wemake-python-styleguide](https://github.com/wemake-services/wemake-python-styleguide) reports short names as
  [WPS111](https://wemake-python-styleguide.readthedocs.io/en/latest/pages/usage/violations/naming.html#wemake_python_styleguide.violations.naming.TooShortNameViolation),
  with a configurable minimum length. It also checks module and attribute names, which this rule does not list.
