0

I am trying to properly type hint my code and encountered both Callable and FunctionType

from typing import Callable
def my_func() -> Callable:
    f = lambda: _
    return f

result = my_func()
type(result)  # <class 'function'>
isinstance(result, Callable)  # True

vs

from types import FunctionType
def my_func() -> FunctionType
    f = lambda: ...
    return f

result = my_func()
type(result)  # <class 'function'>
isinstance(result, FunctionType)  # True

One possible case I can think of is to distinguish between regular and class-based callables like this

class C:
    def __call__(self):
        pass

def my_func() -> Callable:
    c = C()
    return c

result = my_func()
type(result)  # <class 'function'>
isinstance(result, Callable)  # True
isinstance(result, FunctionType)  # False

What are the differences between those and when I have to use one over the other?

GopherM
  • 352
  • 2
  • 8
  • 2
    You should use `Callable` unless you have a **very** good reason to do otherwise. `Callable` accepts generic parameters: say, `Callable[[int, str], None]` is any callable that takes two arguments (int and str) as positional parameters and returns `None`. `types.FunctionType` is more like an internal thing not intended for typechecking. You shouldn't have cases when function is acceptable and instance with `__call__` - not, it's very weird API. – STerliakov Dec 26 '22 at 14:02
  • 1
    Many things you call *don't* have type `function`: types (e.g., `int` and in your example `C`), instance methods (the *class* attribute is a `function`, but the descriptor protocol produces a `method` instance that actually gets called), `functools.partial` instances, built-in functions like `id`, etc. – chepner Jan 06 '23 at 14:17

1 Answers1

1

types.FunctionType is dynamically defined in cpython/Lib/types.py#L11-L12 as the type of the simplest function.

def _f(): pass
FunctionType = type(_f)

typing.Callable on the other hand is defined as a wrapped collections.abc.Callable which a) should be used directly if using python >= 3.9 and b) is itself defined in cpython/Lib/_collections_abc.py#L534-L548 as something having a __call__ method.

class Callable(metaclass=ABCMeta):  # NOTE: I'm skipping most of the definition

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Callable:
            return _check_methods(C, "__call__")
        return NotImplemented

You have highlighted correctly that nearly anything can be a callable, just need to give it a __call__ method, on the other hand, a class will never be a function.

You should use Callable in most cases unless you are completely certain you need a function only.

Moreover, you can (and should) type the arguments and return value of Callable, for instance:

  • Callable[[], None] -- no arg, returns None (e.g. lambda: None)
  • Callable[[int, int], int] -- two int args, returns an int
  • Callable[..., None] -- any arg, returns None, (e.g. print).
ljmc
  • 4,830
  • 2
  • 7
  • 26