Reputation: 246
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?
Upvotes: 16
Views: 3445
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
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
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