Skip to content

wrong positional-or-keyword information in Mapping and MutableMapping #13432

Closed

Description

@beauxq

Mapping and MutableMapping information for get and pop says that the default parameter is positional or keyword.

dict says positional only, and that is correct at runtime.

So this type checks with no errors, but crashes with TypeError:

from collections.abc import Mapping, MutableMapping


def foo(x: MutableMapping[str, int]) -> int:
    return x.pop("3", default=3)


foo({})  # crash: TypeError: dict.pop() takes no keyword arguments


def goo(x: Mapping[str, int]) -> int:
    return x.get("3", default=3)


goo({})  # crash: TypeError: dict.get() takes no keyword arguments


# same with MutableMapping.get

Activity

  1. Daverball commented on Feb 1, 2025

    @Daverball
    Contributor

    Yes, that's because dict violates LSP, which is documented in the stubs:

    typeshed/stdlib/builtins.pyi

    Lines 1125 to 1131 in c193cd2

    # Positional-only in dict, but not in MutableMapping
    @overload # type: ignore[override]
    def get(self, key: _KT, /) -> _VT | None: ...
    @overload
    def get(self, key: _KT, default: _VT, /) -> _VT: ...
    @overload
    def get(self, key: _KT, default: _T, /) -> _VT | _T: ...

    There's two ways to fix this, but both of them would need to happen in CPython, not in typeshed:

    1. We change the implementation of dict, so it accepts the keyword default parameter in addition to a positional one.
    2. We change the implementation of Mapping so it no longer accepts default as a keyword parameter.

    While 1. would be less disruptive, it will potentially add some small overhead to every dict.get call. But changing the API contract of an ABC is a breaking change (especially, because Mapping.get has a default implementation), even if that very same contract is violated in the standard library. So it would be more difficult to push through.

    Prototyping the second change in typeshed would allow us to see if any of the projects we check with mypy_primer rely on the current definition of Mapping.get. A code search will probably not provide anything conclusive, since get is such a generic method name. Although in a quick and dirty search I found at least one instance where people relied on os.environ.get allowing a keyword default, which would be broken, if the default implementation of Mapping.get changed, unless os._Environ provided a new get implementation, which does accept keyword arguments.

    If we can very quickly find one such instance in the standard library, there's a high likelihood there's thousands more in both the standard library and other people's code.

  2. JelleZijlstra commented on Feb 1, 2025

    @JelleZijlstra
    Member

    It probably makes sense for us to claim in typeshed that Mapping/MutableMapping use positional-only arguments, since that's what dict does and in practice it's by far the most common mapping type. I think we do something similar in a few other places already.

  3. GastonAQS commented on Oct 8, 2025

    @GastonAQS
    Contributor

    Hey guys, a bit of an old issue but I was reading it and if I'm not wrong the thing you want to try is this? In Mapping

    @overload
    def get(self, key: _KT, /) -> _VT_co | None: ...
    @overload
    def get(self, key: _KT, default: _VT_co, /) -> _VT_co: ...  # type: ignore[misc] # pyright: ignore[reportGeneralTypeIssues] # Covariant type as parameter
    @overload
    def get(self, key: _KT, default: _T, /) -> _VT_co | _T: ...

    I made all the overloads of get to accept positional parameters only. Maybe I'm missing the big picture

  4. beauxq commented on Nov 25, 2025

    @beauxq
    ContributorAuthor

    I just found out that it's not only dict (by far the most common mapping type),
    but also types.MappingProxyType.get also doesn't allow the default kwarg,
    so it's also probably the 2nd most common mapping type.

  5. adamtheturtle commented on Dec 2, 2025

    @adamtheturtle
    Contributor

    We change the implementation of dict, so it accepts the keyword default parameter in addition to a positional one.

    See python/cpython#124675 which was closed as "not planned".

  6. srittau commented on Dec 2, 2025

    @srittau
    Collaborator

    As mentioned previously, we also usually shouldn't use Mapping in a argument positions and prefer protocols instead. (Same for Sequence.) Cf. PyCQA/flake8-pyi#525

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