Reputation: 1350
I have a custom dict
subclass that is similar to defaultdict but passes the missing key to default_factory
so it can generate an appropriate value.
class KeyDefaultDict(dict):
__slots__ = ("default_factory",)
def __init__(self, default_factory, *args, **kwargs):
super().__init__(*args, **kwargs)
self.default_factory = default_factory
def __missing__(self, key):
if self.default_factory is None:
raise KeyError(key)
ret = self[key] = self.default_factory(key)
return ret
def __repr__(self):
return (
f"{type(self).__name__}({repr(self.default_factory)}, {super().__repr__()})"
)
d = KeyDefaultDict(int)
print(d["1"] + d["2"] + d["3"]) # out: 6
print(d) # out: KeyDefaultDict(<class 'int'>, {'1': 1, '2': 2, '3': 3})
I wanted to add type annotations for this class like the rest of my project, but I couldn't find any example of how to do that.
I saw that the typing
module uses external classes for adding annotations. For example, defaultdict
would be annotated with typing.DefaultDict
whose definition is class typing.DefaultDict(collections.defaultdict, MutableMapping[KT, VT])
.
So it's an external class that subclasses defaultdict
and the generic typing.MutableMapping
.
However, I thought that they probably did it this way because they didn't want to change the original collections.defaultdict
. I found examples of subclasses of Generic
and Mapping
but not classes that inherit from something else like dict
.
The question is: How can I add type annotations for this class to make it a generic class? Do I need to extend something else or make an external class for annotations?
I am using python 3.7.5 and I prefer to inherit directly from dict
so I don't have to implement the required methods and for performance reasons.
Thanks in advance.
Upvotes: 5
Views: 2675
Reputation: 177
I am very late but I just got this working in my own code base.
Basically, you need to use the typing Mapping generic
This is the generic of what dict uses so you can define other types like MyDict[str, int]
.
For my use-case, I wanted a special dict that formatted itself cleanly for logging but I will be using it all over with various types so it needed typing support.
How to:
import typing
# these are generic type vars to tell mapping to accept any type vars when creating a type
_KT = typing.TypeVar("_KT") # key type
_VT = typing.TypeVar("_VT") # value type
# `typing.Mapping` requires you to implement certain functions like __getitem__
# I didn't want to do that, so I just subclassed dict.
# Note: The type you're subclassing needs to come BEFORE
# the `typing` subclass or the dict won't work.
# I had a test fail where my debugger showed that the dict had the items,
# but it wouldn't actually allow access to them
class MyDict(dict, typing.Mapping[_KT, _VT]):
"""My custom dict that logs a special way"""
def __str__(self):
# This function isn't necessary for your use-case, just including as example code
return clean_string_helper_func(
super(MyDict, self).__str__()
)
# Now define the key, value typings of your subclassed dict
RequestDict = MyDict[str, typing.Tuple[str, str]]
ModelDict = MyDict[str, typing.Any]
Now use you custom types of your subclassed dict:
from my_project.custom_typing import RequestDict # Import your custom type
request = RequestDict()
request["test"] = ("sierra", "117")
print(request)
Will output as { "test": ("sierra", "117") }
Upvotes: 4