Skip to content

B008 (function-call-in-default-argument) is overly pedantic; ignore it #214

Closed

Description

@jaraco

Follow-up to #210, which tracks the fallout from Ruff 0.16's expanded default rule set.

The rule

B008 (function-call-in-default-argument) is now on by default. It flags any call in an argument default:

Do not perform function call X in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

Why it isn't worth keeping

The genuine bug in this neighborhood — a shared mutable default — is already caught by B006 (mutable-argument-default), which stays enabled and which I'm happy to keep. What B008 adds is a warning that the call is evaluated once at def time. For an immutable value that isn't a hazard; it's the point.

To keep that from drowning every sensible default, B008 carries a hardcoded allowlist of "immutable calls" — and the allowlist is arbitrary. pathlib.Path() is exempt; every one of its siblings is flagged, despite pure paths being the more obviously immutable ones (they touch no filesystem):

default B008
pathlib.Path() ok
pathlib.PurePath() flagged
pathlib.PosixPath() flagged
pathlib.PurePosixPath() flagged
frozenset() ok
frozendict() (new in 3.15) flagged

A rule that exempts frozenset but not frozendict isn't reasoning about mutability, it's consulting a list that nobody has finished writing.

The escape hatch, extend-immutable-calls, takes fully-qualified names — so suppressing a false positive means enumerating project-specific paths in ruff.toml, which is exactly the downstream config churn the skeleton exists to prevent. For a compatibility shim it's worse: the entry has to name a project-local module (tests.compat.py314.frozendict), and a bare "frozendict" doesn't match the aliased import.

Observed downstream

python/importlib_metadata reports four B008 findings, all benign:

  • importlib_metadata/__init__.py ×2 — def find_distributions(self, context=Context()). This is the public DistributionFinder API and it mirrors CPython's own signature (inspect.signature(importlib.metadata.DistributionFinder.find_distributions) shows the same Context() default). Changing it would diverge the backport from the stdlib it backports.
  • tests/_path.py — def __init__(self, loc=pathlib.PurePosixPath(), record=None), vendored from jaraco.path, so the finding recurs anywhere that file lands.
  • tests/test_main.py — def make_pkg(name, files=frozendict(METADATA="VERSION: 1.0")), written while fixing the B006 report from Ruff 0.16 introduces new default checks #210. Replacing a mutable default with an explicitly immutable one trades B006 for B008, which is a good illustration of the rule having no satisfiable target.

Each suggested remedy is worse than the code it replaces: a None sentinel adds a branch and erases the default from the signature, and a module-level singleton moves the value away from its only use.

Plan

Add B008 to the upstream ignore list in ruff.toml, so downstream projects inherit it rather than each carrying a local exclusion or # noqa:

	# B008 is over-reaching; see jaraco/skeleton#214
	"B008", # function-call-in-default-argument

Activity

  1. added a commit that references this issue on Aug 28, 2026
    2c43cdf
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions