Mapping ABC (and friends): are the mixin methods *required* to satisfy the interface?

I’ve been using ABCs almost since they were added to the language, but there is one point about the built-in ABCs from collections.abc that it occurs to me has never been completely clear to me, and that does not appear to be clearly documented anywhere I can find (at least in the collections.abc docs or in PEP 3319

The question is, in order to safely be considered a Mapping, is it only necessary for a class to implement the required abstract methods (__getitem__, __len__, and __iter__), or is it also necessary to implement all of the so-called “mixin methods”?

I know that if one subclasses Mapping, it’s sufficient to implement just the abstract methods, and then the Mapping class provides implementations of the other methods for free.

But if I have an arbitrary class that does not subclass Mapping, but that at a minimum implements a __getitem__, a __len__, and an __iter__ (with the appropriate semantics, that it iterates over the keys), is it safe to then register it with Mapping.register(MyMappingIshClass)? Or must it also implement __contains__, keys, values, items, get, __eq__, and __ne__?

I can’t find this stated explicitly anywhere…

1 Like

The question here is really one of semantics, since there is no formal requirement (at least, that I can see) for registered classes to actually fulfil the necessary interface.

In order to satisfy the protocol established by an abstract base class, all of the abstract methods defined in the __mro__ of the abstract class need to be implemented. Although one can provide a concrete implementation of __contains__ using __getitem__, as far as the interface is concerned these two methods are separate. To the user, a class inheriting from collections.abc.Mapping should be indistinguishable from one that uses collections.abc.Mapping.register.

In simpler terms, consider collections.abc.Mapping. This interface satisfies Collection i.e. isinstance(collections.abc.Mapping(), Collection) == True (in pseudo code). Therefore, your implementation of a Mapping must also satisfy this interface (i.e. provide __contains__)

Thanks for the reply. Mapping in particular is a tricky case compared to some of the “lower-level” ABCs. For example Sized implements __subclasshook__ in such a way that for any class that has a __len__, issubclass(cls, Sized) returns true.

Likewise for issubclass(cls, Container) for any class that implements __contains__.

But for Mapping there are additional requirements of the interface which cannot be simply checked statically, such as that its __iter__ must return an iterator over the “keys” in mapping. So there is no Mapping.__subclasshook__ that can state simply whether a given class is a Mapping.

My question is really, simply put, for a class to satisfy an ABC interface, must it implement them methods listed in this table from the “Mixin Methods” column. I can’t find anything explicitly stating this. Though I think I did find an answer implicitly:

At least in the case of the Iterator ABC it implements a __subclasshook__ that requires both __next__ and __iter__ to be implemented. In that class, only __next__ is an abstract method, whereas __iter__ is mixin method with a default implementation provided by the Iterator class. But since the __subclasshook__ requires both of them, that implies that the mixin methods must be implemented as well. In the case of Iterator it makes sense because Python requires all iterators to also be iterables.

My question is really, simply put, for a class to satisfy an ABC interface, must it implement them methods listed in this table from the “Mixin Methods” column. I can’t find anything explicitly stating this. Though I think I did find an answer implicitly:

The TL;DR here is that the mixins are just concrete implementations of other abstract interfaces. E.g. Iterator defines a default implementation of __iter__, but it does not define the interface; it is the Iterable ABC which first defines __iter__ (as abstract). The pattern in collections.abc appears to be that the interface is defined in an ABC, from which other interfaces can inherit and define default implementations (mixins). This means that although mixins implement certain interfaces, if you walk the MRO for all classes which define such methods, you’ll eventually end up at an abstract root.

It follows that you have to implement the mixins if you don’t inherit them from a base class; they’re part of the interface.

If I’m overstating the point, let me know, but another way to put this is:

The interface of an ABC is all methods that it defines (abstract or otherwise). From the user’s perspective, if a class inherits from an ABC then we know that it will have a particular set of methods. We don’t care whether those methods are mixins, or user-defined, we just need to have the guarantee that they exist.

Practically, it turns out that all methods are ultimately abstract if you follow the MRO. The fact that the interface you wish to register with has default implementations is a nice bonus, but as far as a non-inheriting class is concerned, those methods still need to be implemented.