1

As a toy example, consider a function that takes two types A, B and returns on object A(B). The standard example for this would be A = defaultdict and B e.g. int. How can I annotate something like this? The closest I got is

from collections import defaultdict
from typing import TypeVar, Type, Protocol

Val = TypeVar('Val')


class InitableMapping(Protocol[Val]):
    """
    A Protocol that enforces a MutableMapping with a special __init__
    """
    def __init__(self, value_type: Type[Val]):
        ...

    def __getitem__(self, item: int) -> Val:
        ...

    def __setitem__(self, key: int, value: Val) -> None:
        ...

    # more methods


M = TypeVar('M', bound=InitableMapping)


def build_mapping(mapping_type: Type[M], value_type: Type[Val]) -> InitableMapping[Val]:
    return mapping_type(value_type)


foo = build_mapping(defaultdict, int)
print(foo.default_factory)  # "InitableMapping[int]" has no attribute "default_factory"

where I put the MyPy output in the comments.

Obviously, the return annotation InitableMapping[Val] is too broad, but M[int, Val] can't work.

Would be glad for any help on this :) In fact even some keywords on what to look for would be great - I had a hard time coming up with a descriptive question title …


Update: A more realistic example might be something like

class MyClass(Generic[M, Val]):
    def __init__(self, load: Callable[[], M[int, Val]]) -> None:
        self.data = load()
CallMeStag
  • 5,467
  • 1
  • 7
  • 22
  • I am thinking of a way to do what you want, but why not do `defaultdict(int)` (or `A(B)`) inline? Is there something in your real scenario that prevents that? – Mario Ishac Dec 23 '20 at 21:47
  • @MarioIshac Sure, the `build_mapping` doesn't have any practical value. I just tried to construct an easy to understand toy example. I added a more realistic one with the same problem. – CallMeStag Dec 24 '20 at 00:03
  • 1
    What you want would be possible with higher-kinded types, but Python's type system isn't that complex. Instead (atleast for your edit's example), you can pass `Callable`s corresponding to the operations you want to do. For example, `Callable[[int], Val]` (getting) or `Callable[[int, Val], None]` (setting). But this is more of a workaround. – Mario Ishac Dec 24 '20 at 02:46
  • Mh, I feared that … `Callable` would work but is even less precise than `Mapping` I guess. Thanks anyway :) – CallMeStag Dec 26 '20 at 18:49
  • Can you please change the example to something that would actually work in theory? A ``Mapping`` supports neither instantiation nor item assignment. – MisterMiyagi Dec 27 '20 at 19:34
  • @MisterMiyagi You're right. Updated the example. – CallMeStag Dec 28 '20 at 21:45

0 Answers0