4

I want to define a typing protocol for a custom mapping class. This class needs to be very similar to a MutableMapping, except that it has a couple of additional methods beyond those those that collections.abc.MutableMapping defines as abstract methods (specifically .copy()), and I also want to specify in advance which types any implementations of the custom mapping class must use as keys and values.

After reading through PEP 544 I thought I should be able to do this:

from typing import Hashable, MutableMapping, Protocol, TypeVar


TVarMapProt = TypeVar("TVarMapProt", bound="VariableMappingProtocol")


class VariableMappingProtocol(MutableMapping[Hashable, int], Protocol):
    """
    Protocol for the type behaviour of a class which
    acts basically like a dict of int objects.
    """

    def copy(self: TVarMapProt) -> TVarMapProt:
         # TODO replace return type with Self if PEP 673 goes through
        ...

The idea being that in my code I can state that a type VariableMappingProtocol is expected, and then the user would have to use their own class that was defined like this in order to avoid a typing error:

TCusVarMap = TypeVar("CusVarMap", bound="CustomVariableMapping")


class CustomVariableMapping
    """Defines all the methods that a MutableMapping does, but mapping Hashables to ints"""

    def __getitem__(self, key: Hashable) -> int:
        # implementation goes here
        ...

    # etc. for __setitem__, and so on...

    def copy(self) -> TCusVarMap:
        # implementation goes here
        ...

The problem is that when I run mypy on the code defining VariableMappingProtocol I get this error:

test.py:7: error: All bases of a protocol must be protocols
Found 1 error in 1 file (checked 1 source file)

If I delete MutableMapping so that VariableMappingProtocol only inherits from Protocol then the error goes away. But then I'm not getting the structural typing I want for all the abstract methods from MutableMapping.

So it seems the problem is that typing.MutableMapping is not considered a Protocol? But that's weird, especially as I can treat some of the other types in typing as Protocols, e.g. this example (from PEP 544)

from typing import Protocol, Sized


class SizedProtocol(Sized, Protocol):
    """
    Protocol for the type behaviour of a class which has a __len__ method.
    """
    ...

which doesn't throw any mypy errors.

How can I inherit from MutableMapping as a Protocol, and thus avoid the need to write out all the methods of MutableMapping in my VariableMappingProtocol definition?

ThomasNicholas
  • 1,273
  • 11
  • 21
  • 1
    [`typing.MutableMapping` is deprecated](https://docs.python.org/3/library/typing.html#typing.MutableMapping), as it was just a generic version of `collections.abc.MutableMapping`, like `typing.Dict` was for `dict`, and now the original types support being used generically so the version in `typing` is superfluous. So `MutableMapping` is an [abstract base class](https://docs.python.org/3.7/glossary.html#term-abstract-base-class), not a protocol. – Jasmijn Dec 18 '21 at 23:47
  • Huh, thank you @Jasmijn ! So is there a way to get the behavior I want now? – ThomasNicholas Dec 18 '21 at 23:50
  • 1
    I'm still looking, but I'm afraid you'll have to copy the definition of `MutableMapping` into your protocol. By the way, I was not entirely correct: `collections.abc.Sized` (for which `typing.Sized` is a (apparently non-deprecated) alias) is also an abstract base class, but it is [explicitly marked as okay as a base class for protocols](https://github.com/python/cpython/blob/3.10/Lib/typing.py#L1456-L1462). From what I can tell, the difference is because `MutableMapping` implements mixin methods like `pop`, `update` and `clear`. and it doesn't make sense for protocols to implement any methods. – Jasmijn Dec 18 '21 at 23:57
  • Iterator can be used as Protocol and has a concrete method: `__iter__`. – hussic Dec 19 '21 at 16:15

0 Answers0