Search code examples
pythonpycharmpython-typing

types.FunctionType vs typing.Callable?


What are the drawbacks or benefits to using types.FunctionType vs typing.Callable as a type-hint annotation?

Consider the following code...

import types
import typing

def functionA(func: types.FunctionType):
    rt = func()
    print(func.__name__)
    return rt

def functionB(func: typing.Callable):
    rt = func()
    print(func.__name__)
    return rt

The only difference I can see is Callable could be any sort of callable object (function, method, class, etc) while FunctionType is limited to only functions.
Am I overlooking something? Is there a benefit to using FunctionType over Callable in certain situations?


Solution

  • The types module predates PEP 484 annotations and was created mostly to make runtime introspection of objects easier. For example, to determine if some value is a function, you can run isinstance(my_var, types.FunctionType).

    The typing module contains type hints that are specifically intended to assist static analysis tools such as mypy. For example, suppose you want to indicate that a parameter must be a function that accepts two ints and returns a str. You can do so like this:

    def handle(f: Callable[[int, int], str]) -> None: ...
    

    There is no way to use FunctionType in a similar manner: it simply was not designed for this purpose.

    This function signature is also more flexible: it can also accept things like objects with a __call__ since such objects are indeed callable.

    The contents of the typing module can also sometimes be used for runtime checks in a manner similar to the contents of types as a convenience: for example, doing isinstance(f, Callable) works. However, this functionality is deliberately limited: doing isinstance(f, Callable[[int, int], str]) is intentionally disallowed. Attempting to perform that check will raise an exception at runtime.

    That said, I don't think it's a good style to perform runtime checks using anything from typing: the typing module is meant first and foremost for static analysis.

    I would not use anything from the types module within type hints for similar reasons. The only exception is if your function is written in a way such that it's critical that the value you need to receive is specifically an instance of FunctionType, rather than being any arbitrary callable.