All bases of a protocol must be protocols- is typing.MutableMapping not a Protocol?
Asked Answered
D

0

9

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?

Draftee answered 18/12, 2021 at 23:32 Comment(5)
typing.MutableMapping is deprecated, 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, not a protocol.Anarthria
Huh, thank you @Anarthria ! So is there a way to get the behavior I want now?Draftee
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. 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.Anarthria
Iterator can be used as Protocol and has a concrete method: __iter__.Steve
I just had a similar problem, where I tried to create a class MyClass(Mapping, Protocol):, and I got it working by removing the Protocol, i.e, I'm interiting only from Mapping (class MyClass(Mapping):) and it seems it behaves like a Protocol, so maybe that's the way..Decalogue

© 2022 - 2024 — McMap. All rights reserved.