Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

User defined generic types and collections.abc

I have a Python package that defines a variety of collections based on the ABCs provided by collections.abc (Mapping, Sequence, etc). I want to take advantage of the type hinting facilities introduced in Python 3.5, but I'm having doubts as to what would be the best way to go about it.

Lets take one of these classes as an example; until now, I had something resembling this:

from collections.abc import Mapping

class MyMapping(Mapping):
    ...

To turn this into a generic type, the documentation suggests doing something like this:

from typing import TypeVar, Hashable, Mapping

K = TypeVar("K", bound=Hashable)
V = TypeVar("V")

class MyMapping(Mapping[K, V]):
    ...

But this poses two problems:

  • The class looses all the mixin methods from collections.abc.Mapping. I could deal with this implementing them myself, but that would defeat part of the purpose of using ABCs in the first place.

  • isinstance(MyMapping(), collections.abc.Mapping) returns False. Also, trying to call collections.abc.Mapping.register(MyMapping) to work around this raises a RuntimeError ("Refusing to create an inheritance cycle").

My first attempt to solve these problems was to go back to extending collections.abc.Mapping:

from typing import TypeVar, Hashable
from collections.abc import Mapping

K = TypeVar("K", bound=Hashable)
V = TypeVar("V")

class MyMapping(Mapping[K, V]):
    ...

But that doesn't work, as collections.abc.Mapping is not a generic type and does not support the subscription operator. So I tried this:

from typing import TypeVar, Hashable, Mapping
from collections.abc import Mapping as MappingABC

K = TypeVar("K", bound=Hashable)
V = TypeVar("V")

class MyMapping(MappingABC, Mapping[K, V]):
    ...

But this smells fishy. The imports and aliasing are cumbersome, the resulting class has a tortuous MRO, and the mix-in methods provided by the ABC won't have typing information...

So what is the preferred way to declare a custom generic type based on a collection ABC?

like image 758
Marti Congost Avatar asked Oct 14 '15 07:10

Marti Congost


1 Answers

[ Personal Opinion™ ]: I don't really support creating new typing features. These should be generic enough to not require any modification on your code. If your mapping class is so sophisticated it cannot be replaced by any common mapping (like dict), you are better off just using itself:

def foo(bar: MyMapping) -> List:
    pass

instead of

def foo(bar: Mapping[K, V]) -> List:
    pass

Now, if you want your users to be able to "type" check your class with typing.Mapping, you just need to subclass collections.Mapping

class MyMapping(collections.abc.Mapping):
    ... # define required methods

isinstance(MyMapping(), typing.Mapping[K, V]) # --> True
like image 113
JBernardo Avatar answered Sep 29 '22 12:09

JBernardo