Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python Dictionary with generic keys and Callable[T] values

Tags:

I have some named tuples:

JOIN = NamedTuple("JOIN", [])
EXIT = NamedTuple("EXIT", [])

I also have functions to handle each type of tuple with:

def handleJoin(t: JOIN) -> bool:
    pass

def handleExit(t: EXIT) -> bool:
    pass

What I want to do is create a dictionary handleTuple so I can call it like so:

t: Union[JOIN, EXIT] = #an instance of JOIN or EXIT
result: bool
result = handleTuple[type(t)](t)

What I cannot figure out is how to define said dictionary. I tried defining a generic T:

T = TypeVar("T", JOIN, EXIT)
handleTuple: Dict[T, Callable[[T], bool]

However I get an error saying "Type variable T is unbound" which I do not understand. The closest I have got so far is:

handleTuple: Dict[Type[Union[JOIN, EXIT]], bool]
handleTuple = {
    JOIN: True
    EXIT: False
}

This works fine for calling it the way I want, however I cannot figure out the Callable part in the definition so I can include my functions. How can I do this

like image 620
stelioslogothetis Avatar asked Dec 02 '20 11:12

stelioslogothetis


People also ask

What is generic T in Python?

Generic is a programming library for Python that provides tools for generic programming. By now, there is only one feature – multiple dispatch.

Can Python dictionary have different data types?

Almost any type of value can be used as a dictionary key in Python. You can even use built-in objects like types and functions.

What does .values 0 do in Python?

values()[0] to pull out the first value of a list inside a dictionary.

When should I use TypedDict?

The TypedDict allows us to describe a structured dictionary/map with an expected set of named string keys mapped to values of particular expected types, which Python type-checkers like mypy can further use.


1 Answers

TypeVars are only meaningful in aliases, classes and functions. One can define a Protocol for the lookup:

T = TypeVar("T", JOIN, EXIT, contravariant=True)

class Handler(Protocol):
    def __getitem__(self, item: Type[T]) -> Callable[[T], bool]:
        ...

handleTuple = cast(Handler, {JOIN: handleJoin, EXIT: handleExit})

The special method self.__getitem__(item) corresponds to self[item]. Thus, the protocol defines that accessing handleTuple[item] with item: Type[T] evaluates to some Callable[[T], bool]. The cast is currently needed for type checkers such as MyPy to understand that the dict is a valid implementation of this protocol.


Since the code effectively implements a single dispatch, defining a functools.singledispatch function provides the behaviour out of the box:

@singledispatch
def handle(t) -> bool:
    raise NotImplementedError

@handle.register
def handleJoin(t: JOIN) -> bool:
    pass

@handle.register
def handleExit(t: EXIT) -> bool:
    pass

t: Union[JOIN, EXIT]
result: bool
result = handle(t)
like image 102
MisterMiyagi Avatar answered Sep 18 '22 15:09

MisterMiyagi