Type hint generic function with arguments of passed function

Question:

I would like to write type hints that allow type checkers to recognize if a wrong argument is passed to a function I call "meta" in the example code below:

I have got the following functions:

  • a function meta that takes a function and arguments for that function as its arguments,
  • an example function example_function with which I demonstrate configurations that should produce warnings / errors (PEP484):
from typing import TypeVar, Callable

def example_function(f: str, g: int) -> None:
    assert isinstance(f, str)
    assert isinstance(g, int)


In = TypeVar('In')
Out = TypeVar('Out')
def meta(func: Callable[In, Out], *args, **kwargs) -> Out:
    a = func(*args, **kwargs)
    print(f'{func.__name__} executed' )
    return a

The following calls should be fine:

# ok
meta(example_function, f='', g=0)

The following calls should produce warnings:

# arg for f already provided
try:
    meta(example_function, '', f='', g=0)
except:
    ...

# too many *args 
try:
    meta(example_function, '', 0, 1)
except:
    ...

# too many **kwargs
try:
    meta(example_function, f='', g=0, k='diff')
except:
    ...

# invalid kwarg / arg
try:
    meta(example_function, 0, g='')
except:
    ...

Is what I want to achieve possible with a mixture of arguments and keyword arguments?
If not, is it possible with keyboard arguments / arguments only?

Asked By: mutableVoid

||

Answers:

You can use ParamSpec to match the *args and **kwargs from the func (emphasis mine):

Parameter specification variables exist primarily for the benefit of
static type checkers. They are used to forward the parameter types of
one callable to another callable
– a pattern commonly found in higher
order functions and decorators.

In use:

T = TypeVar('T')
P = ParamSpec('P')

def meta(func: Callable[P, T], *args: P.args, **kwargs: P.kwargs) -> T:
    a = func(*args, **kwargs)
    print(f'{func.__name__} executed' )
    return a

This errors on all of the cases you mention:

# OK
meta(example_function, f='', g=0)

# main.py:18: error: "meta" gets multiple values for keyword argument "f"  [misc]
meta(example_function, '', f='', g=0)

# main.py:19: error: Too many arguments for "meta"  [call-arg]
meta(example_function, '', 0, 1)

# main.py:20: error: Unexpected keyword argument "k" for "meta"  [call-arg]
meta(example_function, f='', g=0, k='diff')

# main.py:21: error: Argument 2 to "meta" has incompatible type "int"; expected "str"  [arg-type]
# main.py:21: error: Argument "g" to "meta" has incompatible type "str"; expected "int"  [arg-type]
meta(example_function, 0, g='')

MyPy playground

Answered By: jonrsharpe
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.