Reputation: 570
I'm trying to create a dict-like type with setdefault()
in place of get()
, with pre-defined default type. But it also should not be a pointer to same object all the time.
Problem is, I don't understand how to instantiate a var given a corresponding TypeVar.
Here is pseudo code:
F = TypeVar('F')
T = TypeVar('T')
class AutoExpandingDict(Dict[F, T]):
def __getitem__(self, key: F) -> T:
if key not in self:
self[key] = new T()
return super(AutoExpandingDict, self).__getitem__(key)
how do I make new T()
work?
Expected use is:
stats = AutoExpandingDict[str, AutoExpandingDict[str, RequestTimer]]()
where RequestTimer is a class which records some statistics, then
def _api_request(method = 'GET', endpoint = None):
...
with stats[method][endpoint]:
...
response = self.session.request(method, f'{self.base_url}{api_path}{url_params_encoded}', ...)
...
...
There will be other uses aside from RequestTimer
, and I don't want to copy-paste a lot of classes with just different names or repeat setdefault
with magic parameters every time (if it will be a plain dict
).
Upvotes: 0
Views: 574
Reputation: 3083
Generic type parameters in Python are "erased" at runtime, so you can't access the value of T
. Consider this:
A = TypeVar("A", covariant=True)
class Foo(Generic[A]):
def __init__(self, something: A) -> None:
self._something = something
def do_something(self):
[a_type] = get_params_somehow(self)
print(a_type)
class X:
pass
class Y(X):
pass
foo1: Foo[X] = Foo(Y())
foo2: Foo[Y] = Foo(Y())
x = some().com().pu().ta() + tion()
foo3 = Foo(x)
foo1
an foo2
were created in the same way, and they can't access the information about their type (without resorting to hacks with inspect
).
With foo3
, the type of x
will be inferred by a type checker, so foo3
can't really know what it is at runtime, and the type might be different with mypy
, pyright
, pyre
etc. Again, the types (not runtime type, but the type hint stuff) are just "in the type checker's head".
If you only intend to construct the dict in the AutoExpandingDict[str, AutoExpandingDict[str, RequestTimer]]()
manner, though, you could override __class_getitem__
or make a custom metaclass. But that is not very intuitive and probably overkill
What you might want to do is provide a factory, similar to collections.defaultdict
:
class AutoExpandingDict(Dict[F, T]):
def __init__(self, factory: Callable[[], T]):
self._factory = factory
def __getitem__(self, key: F) -> T:
if key not in self:
self[key] = self._factory()
return super().__getitem__(key)
For example:
stats: AutoExpandingDict[str, AutoExpandingDict[str, RequestTimer]] =
AutoExpandingDict(lambda: AutoExpandingDict(RequestTimer))
Maybe you just want a defaultdict
?
Upvotes: 1