AsukaMinato
AsukaMinato

Reputation: 1442

How to write type hints for a function returning itself?

from typing import Callable


def f() -> Callable:
    return f

How to explicitly define f's type? like Callable[[], Callable]

I think it is slightly like a linked list, but I can't implement it.

from typing import Union


class Node:
    def __init__(self, val):
        self.val = val
        self.next: Union[Node, None] = None

Upvotes: 4

Views: 1684

Answers (2)

flakes
flakes

Reputation: 23674

I think @chepner's answer is great. If you really do want to express this as a recursive Callable type, then you could restructure the function as a callable class and do something like this:

from __future__ import annotations


class F:
    def __call__(self) -> F:
        return self


f = F()

You can test this with mypy to see that it maintains its type on future calls:

g = f()
h = g(1)  # Too many arguments for "__call__" of "F"
i = h()
j = i(2)  # Too many arguments for "__call__" of "F"
k = j()

Upvotes: 5

chepner
chepner

Reputation: 532238

Ideally, you could use typing.Literal to specify the exact function being returned, but that doesn't work for a number of reasons:

  1. There's no way to refer to f using typing.Literal. typing.Literal['f'] is the string literal 'f', not a forward reference to the function about to be defined.

  2. f doesn't necessarily refer to the function being defined. It's a free variable that could refer to something else entirely by the time the function is actually called.

OK, let's loosen our restriction. Let's try to define a type of function that returns a function of its own type. We can at least write a valid type hint:

T = typing.Callable[[],'T']

def f() -> T:
    return f

but this is only useful as documentation; mypy doesn't yet know how to handle recursively defined types like this.

So let's try

T1 = Callable[[], 'T2']
T2 = Callable[[], T1]

def f() -> T2:
    return f

Oops, mypy doesn't like cyclic definitions, either.

So we're left with your original idea, of using the overlay broad type of functions that take no arguments and returns some callable.

def f() -> Callable[[], Callable]:
    return f

The problem with the comparison to Node is that there's no type-level recursion involved; next simply refers to some value of type Node.

Upvotes: 4

Related Questions