9

I have a lot of functions that have the same signature, let's say (int, int) -> int.

Is there a way to type these functions with a Callable (or something else) to avoid specifying the types of the parameters and the return type for each of these functions? I would like to do something like that (but it obviously fails):

from typing import Callable

f: Callable[[int, int], int]
def f(x, y):  #  with the previous line, this is equivalent to 'def f(x: int, y: int) -> int:'
    ...

Running mypy results in:

file.py:4: error: Name "f" already defined on line 3
Found 1 error in 1 file (checked 1 source file)
Alex Waygood
  • 6,304
  • 3
  • 24
  • 46
qouify
  • 3,698
  • 2
  • 15
  • 26
  • 2
    Does this answer your question? [python How to define a type for a function (arguments and return type) with a predefined type?](https://stackoverflow.com/questions/65182608/python-how-to-define-a-type-for-a-function-arguments-and-return-type-with-a-pr) – MisterMiyagi Sep 02 '21 at 12:01
  • Yes, it does. Thank you. Using your decorator solution `mypy --strict` still complains with `Function is missing a type annotation` on the decorated function, but I guess there is no solution for that (other than disabling the type check for that line). – qouify Sep 02 '21 at 12:16
  • @MisterMiyagi: but then using your decorator, mypy cannot guess the type of parameters in the decorated functions or am I wrong? In your example, if I do `x: int = name` in `any_greet_person`, mypy won't complain. As I understand it, only the definition of your decorator (`def copy_signature(template: C) -> Callable[[C], C]:`) helps mypy to guess that the type of `any_greet_person` is `(name: str, age: int) -> str` but this is only *outside* `any_greet_person`, isn't it? – qouify Sep 02 '21 at 12:56
  • Indeed the "inside" of the function stays untyped with the approach shown in [my answer](https://stackoverflow.com/questions/65182608/python-how-to-define-a-type-for-a-function-arguments-and-return-type-with-a-pr/65288734#65288734). While I don't know a solution to that (indeed I think it is impossible to change), you are correct that it is notably different from your desired behaviour. – MisterMiyagi Sep 02 '21 at 12:59
  • @MisterMiyagi: ok, thanks for your clear answers. Last thing: what's the point of doing `setattr(target, "__annotations__", getattr(template, "__annotations__"))`, is it for dynamic type checking? Because as I understand it, mypy does not care about that, right? – qouify Sep 02 '21 at 13:03
  • Yes, some tooling inspect the annotations at runtime. For example, ``functools.singledispatch`` uses annotations to dispatch based on types. (It's also a rather convoluted way to do that – will edit it right away.) – MisterMiyagi Sep 02 '21 at 13:08

1 Answers1

1

Maybe there's a more elegant solution. Not recommended but you can set the <function>.__annotations__. More info about it here.

from typing import get_type_hints

callable_int_annotations = {"x": int, "y": int, "return": int}

def f_with_hint(x: int, y: int) -> int:
    return x + y

def f_without_hint(x, y):
    return x + y

print(f"Before {get_type_hints(f_with_hint)=}")
print(f"Before {get_type_hints(f_without_hint)=}")

f_without_hint.__annotations__ = callable_int_annotations

print(f"After {get_type_hints(f_with_hint)=}")
print(f"After {get_type_hints(f_without_hint)=}")
Before get_type_hints(f_with_hint)={'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}
Before get_type_hints(f_without_hint)={}
After get_type_hints(f_with_hint)={'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}
After get_type_hints(f_without_hint)={'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}
  • 2
    Heads up that this only manages the *runtime* annotations. A static type checker like mypy used in the question will not "see" these. – MisterMiyagi Sep 02 '21 at 12:01
  • Thanks for the suggestion. I didn't know of the `__annotations__` attribute. My primarly goal however was indeed to make mypy happy with the definition of the function. – qouify Sep 02 '21 at 12:18