Marti Congost
Marti Congost

Reputation: 246

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:

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?

Upvotes: 16

Views: 3445

Answers (3)

InSync
InSync

Reputation: 10437

Now that typing.Mapping is deprecated and its functionalities are "transferred" to collections.abc.Mapping, this will work without a hitch:

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

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

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

This has been possible ever since Python 3.9 via PEP 585.

Or, if you prefer PEP 695/Python 3.12+ syntax:

from collections.abc import Mapping, Hashable

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

Upvotes: 1

max
max

Reputation: 52235

Your original code works fine with the current version of python and mypy, and does everything exactly as you want (including reusing implementation from collections.abc.Mapping).

However, for the time being, you should remove the bound=Hashable, since it's not fully supported yet:

from typing import TypeVar, Hashable, Mapping

K = TypeVar("K")
V = TypeVar("V")

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

Upvotes: 1

JBernardo
JBernardo

Reputation: 33397

[ 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

Upvotes: 0

Related Questions