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?
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.