python / python/cpython

[docs] dictionary unpacking `fn(**kwargs)` should accept `SupportsKeysAndGetitem` and not just `Mapping`

Open
#157,747 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

docs
Dominant language
Python
Stars
77.2k
Forks
35.9k
PR merge metrics
PR metrics pending

Description

Brief

The docs say that for dict unpacking as in fn(**kwargs), kwargs must "implement the methods specified in the collections.abc.Mapping", when in reality only keys() and __getitem__ as in the dict constructor are required.

Documentation

The current documentation states that:

[6.2.5.4. Dictionary displays]
Instead of a key-value pair, a dict item may be an expression prefixed by a double asterisk **. This denotes dictionary unpacking. At runtime, the expression must evaluate to a mapping;

which points to

[mapping]
A container object that supports arbitrary key lookups and implements the methods specified in the collections.abc.Mapping or collections.abc.MutableMapping abstract base classes.

However, at runtime, fn(**kwargs) works whenever kwargs supports both a keys() -> Iterable[str] and __getitem__(self, key: str) -> object method:

class SupportsKeysAndGetitem:
    _dict = {"foo": 0, "bar": object()}

    def keys(self):
        return iter(self._dict)

    def __getitem__(self, key: str, /) -> object:
        return self._dict[key]

def expects_kwargs(**kwargs: int) -> None:
    print(kwargs)

d = SupportsKeysAndGetitem()
expects_kwargs(**d)  # works at runtime

Moreover, the relevant methods that Cpython executes under the hood explicitly supports this looser protocol:

class dict
If a positional argument is given and it defines a keys() method, a dictionary is created by calling getitem() on the argument with each returned key from the method.

and

int PyDict_Merge(PyObject *a, PyObject *b, int override)
Part of the Stable ABI. Thread safety: Safe for concurrent use on the same object.
Iterate over mapping object b adding key-value pairs to dictionary a. b may be a dictionary, or any object supporting PyMapping_Keys() and PyObject_GetItem().

Proposal

Update the documentation of dict-unpacking to reflect the actual runtime behavior in sync with how dict construction works, only requiring keys() and __getitem__ rather than inheritance from the non-protocol class collections.abc.Mapping.

Rationale

  • fn(**kwargs) only requiring keys() and __getitem__ is how it works at runtime, and has since ages
  • Supporting a protocol is nicer than just supporting a non-protocol ABC like collections.abc.Mapping due to duck-typing (a similar conclusion was reached in the discussion of #116938)
  • There are real world use-cases like fn(**dataframe) as for example a pandas DataFrame supports keys() (yields the list of columns) and __getitem__ (look up column by name), but does not inherit from the collections.abc.Mapping class, nor implements all of its methods.
Linked PRs
  • gh-157771

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Review the linked PR gh-157771 alongside the Dictionary displays and mapping documentation cited in the issue. Verify the wording against the described keys() and getitem runtime behavior; done means the docs no longer require Mapping inheritance and remain consistent with dict construction.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
documentation
Issue type
Documentation
Difficulty
1/5
Estimated time
1-3 hours
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.