Following the theme of my previous two posts, I’ve run into another typing conundrum where I want to unpack a pre-existing Callable into a class with Generic[P, T] where P is a parameter specification type (i.e. ParamsSpec)

After figuring out the right way to declare a generic featuring a ParamSpec, I updated the class-resolver package to use the shiny new (and more accurate) annotations. Unfortunately, reality set in, and within hours, someone reported this caused errors in PyKEEN, my graph machine learning software package that heavily uses class-resolver to make its knowledge graph embedding models modular and configurable.

I was able to fix PyKEEN, but the fact that I was previously using a named type alias for Normalizer: TypeHint = Callable[[torch.Tensor], torch.Tensor] and then had to re-write it yet again the variable declaration’s type hint as FunctionResolver[[torch.Tensor], torch.Tensor] was not great. What I would love is some way to unpack Normalizer into the arguments of FunctionResolver[...].

Let me demonstrate in a more self-contained way:

from typing import Callable, Generic, Unpack, ParamSpec, TypeVar

P = ParamSpec("P")
T = TypeVar("T")

class Box(Generic[P, T]):
    def __init__(self, func: Callable[P, T]) -> None:
        self.func = func

def f(x: int) -> str:
    return str(x)

# This works!
box_1: Box[[int], str] = Box(f)

FType = Callable[[int], str]

# I wish I could do this, in case I already
# had a type variable referring to Callable[[int], str]
box_2: Box[Unpack[FType]] = Box(f)

While I’m abusing the usage of typing.Unpack, some way of extracting the arguments of one type and splatting them into another seems like it might have a use case. Maybe.