Skip to content

Keep positional parameters positional when partial() gets **dict - #22338

Closed
udsy19 wants to merge 1 commit into
python:masterfrom
udsy19:fix/partial-dict-kwargs-positional
Closed

udsy19 wants to merge 1 commit into
python:masterfrom
udsy19:fix/partial-dict-kwargs-positional

Conversation

@udsy19

@udsy19 udsy19 commented Oct 31, 2026

Copy link
Copy Markdown

Fixes #18644

When functools.partial() receives a **mapping argument, the plugin turned every positional parameter the mapping could fill into a keyword-only one. A plain dict may not contain those keys, so passing them positionally afterwards is fine at runtime, and mypy already accepts the equivalent direct call:

from functools import partial
from typing import Any

def f(a: int, b: str, c: bytes = b"") -> None: ...

d: dict[str, Any] = {}
f(1, "x", **d)      # OK
p = partial(f, **d)
p(1, "x")           # before: error: Too many positional arguments for "f"  [call-arg]

Now a positional parameter that is matched only by non-TypedDict ** arguments stays positional (and optional) in the partial's signature. Parameters given by explicit keyword (partial(f, b="x")) and parameters filled from a TypedDict ** (whose keys mypy treats as present, as in direct calls) remain keyword-only, unchanged.

The Argument 1 has incompatible type "**dict[str, int]" error from the issue's first example is unchanged: the direct call reports the same thing.

Test: testFunctoolsPartialDictUnpackKeepsPositional in check-functools.test covers positional/partial/keyword calls after partial(fn, **d), a function with / and *args, mixing a positional argument with **d, errors that must still be reported, and the explicit-keyword case. It fails on master and passes with the change; check-functools.test and the full testcheck.py suite pass locally.

I used AI assistance while preparing this change; I reviewed the diff and ran the tests above.

When functools.partial() received a **mapping, every
positional-or-keyword parameter that the mapping could fill was made
keyword-only in the partially applied signature. A plain dict may not
contain those keys, so calling the result positionally is fine at runtime
and mypy already accepts the equivalent direct call f(1, "x", **kwargs).
This led to false "Too many positional arguments" errors.

Keep such parameters as optional positional-or-keyword parameters. A
TypedDict is still treated as providing its keys, matching how mypy
checks direct calls, and an explicit keyword argument still makes the
parameter keyword-only.

Fixes python#18644

Signed-off-by: Udaya Tejas <udayatejas2004@gmail.com>
@github-actions

Copy link
Copy Markdown
Contributor

According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅

@ilevkivskyi ilevkivskyi closed this Nov 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Passing **dict(...) to functools.partial confuses mypy

2 participants