skip to content

Why is `callable(x)` preferred over `isinstance(x, types.FunctionType)` in Python?

level: middleimportance: should knowfreq 40%

answer

  1. Two ways to ask the same question
  2. def and lambda are not the only callables
  3. Special methods are found on the type
  4. callable(x) tests type(x) for __call__
  5. partial objects and classes fail the type check

basics

~10 s

callable(x) asks whether type(x) defines __call__, so it accepts classes, built-in functions, bound methods, functools.partial objects and instances with __call__. types.FunctionType matches only def and lambda objects, rejecting most callables real code passes around.

solid answer

~40 s

Calling is a protocol, so test the protocol. `callable(x)` reports whether `type(x)` supplies a `__call__` implementation, which is exactly the condition the call syntax uses. `isinstance(x, types.FunctionType)` instead asks whether the object came out of a `def` or a `lambda`, and that excludes classes, built-ins like `len`, bound methods, method descriptors, `functools.partial` objects and any instance whose class defines `__call__` — most of what a dispatch table or a higher-order helper is actually handed. Note that `callable` is necessary, not sufficient: `True` does not promise the call will succeed with your arguments, so pair it with `inspect.signature` when arity matters. Narrow deliberately only when you need function-only machinery, using `inspect.isfunction` or the wider `inspect.isroutine`.

code

pycon · 9 lines
pycon
>>> import functools, types
>>> class Shout:
...     def __call__(self, s): return s.upper()
...
>>> handlers = [len, Shout(), functools.partial(sorted, reverse=True), str.strip, lambda s: s]
>>> [callable(h) for h in handlers]
[True, True, True, True, True]
>>> [isinstance(h, types.FunctionType) for h in handlers]
[False, False, False, False, True]

go deeper

for a junior

Be ready to say that callable(x) tells you whether an object can be called, and to list things other than plain functions that pass it: classes, built-ins like len, and methods. Knowing that a class is callable is the piece juniors most often miss.

for a middle

Explain the mechanism: callable checks type(x) for a __call__ implementation, which is the same lookup the call syntax uses, while types.FunctionType is a concrete type that only def and lambda produce. Name the callables the type check wrongly rejects.

for a senior

Show the production judgment: a registry or dispatch layer that type-checks its handlers rejects partials, bound methods and configurable callable objects, and if it skips instead of raising it degrades quietly. Pair callable with inspect.signature when arity is part of the contract.

for a principal

Own the API-design tradeoff: how strictly a framework should validate the callables it accepts, when a documented protocol beats a runtime type check, and when static typing should carry the contract instead of a defensive isinstance that only narrows what callers may pass.

### Two tests that look alike and ask different things `callable(x)` is a builtin that answers a **protocol** question: *does calling this object mean anything?* It checks whether the object's **type** supplies a `__call__` implementation. `isinstance(x, types.FunctionType)` answers a **type-identity** question: *was this object produced by a `def` statement or a `lambda` expression?* The two sets overlap, but the second is far smaller, and almost everything in the gap is a callable that real code hands to a higher-order helper. ### What the type test silently rejects `types.FunctionType` — the very same object as `types.LambdaType` — is the type of Python-level functions and nothing else. It is `False` for: * **classes.** A class is callable; calling it runs instantiation and returns an instance. Factories are routinely passed where a function is expected. * **built-in functions.** `len`, `print`, `sorted` and friends are `types.BuiltinFunctionType`. * **method descriptors.** `str.strip`, reached through the class rather than an instance, is `types.MethodDescriptorType`. * **bound methods.** `obj.handle` is `types.MethodType`, a wrapper that holds the underlying function plus the instance. * **`functools.partial` objects.** A partial is an instance of a C-implemented class that defines `__call__`; the function it wraps is buried inside it. * **instances of classes that define `__call__`.** This is the ordinary way to write a callable that carries state or configuration. * **test doubles.** A `unittest.mock.Mock` is callable and is not a function. A plugin registry that guards its input with `isinstance(handler, types.FunctionType)` therefore turns away most of the handlers its callers legitimately supply. The failure is usually noisy — a `TypeError` at registration time — but it can also be quiet: a registry that *skips* anything failing the type test instead of raising will register a subset of the handlers it was given, and a search index rebuilt from that subset comes back silently truncated rather than broken. That class of bug is exactly why the protocol test is the default. ### Special methods are looked up on the type, not the instance `callable(x)` reports on `type(x)`, and so does the call syntax itself. Assigning `obj.__call__ = some_function` puts an entry in the instance `__dict__` and changes nothing: `obj()` still raises `TypeError: 'Handler' object is not callable`, and `callable(obj)` stays `False`. The rule is general — implicit special-method lookup bypasses the instance — and this is one of the places where a candidate's mental model shows. To make instances callable you define `__call__` in the class body. ### `callable` is necessary, not sufficient `callable(x)` being `True` says only that the object has a call implementation. It does not promise that `x(payload)` will succeed: the arity may be wrong, the argument types may be wrong, and `__call__` may raise on its own. It also does not promise the call is cheap or side-effect free — calling a class constructs an object. So `callable` belongs at the boundary as a fast rejection of obvious nonsense, with `inspect.signature` doing the real contract check when arity matters: ```python import inspect def register(handler): if not callable(handler): raise TypeError(f"handler must be callable, got {type(handler).__name__}") if len(inspect.signature(handler).parameters) != 1: raise TypeError("handler must take exactly one argument") return handler ``` The negative direction is the strong one: `callable(x)` returning `False` really does mean the object cannot be called. ### When a narrower test is the right one Narrowing is not always wrong — it is wrong as a stand-in for "is this callable". Reach for the narrow test when you are about to do something that only works on a Python-level function: * `inspect.isfunction(x)` is exactly the `types.FunctionType` check, spelled so the intent is obvious. Use it when you are about to touch function-only machinery. * `inspect.isroutine(x)` is the wider "function-like" test: it accepts Python functions, lambdas, built-in functions, bound methods and method descriptors, while still excluding classes and arbitrary objects with `__call__`. * `isinstance(x, collections.abc.Callable)` is the abstract-base-class spelling of `callable(x)`; its subclass hook checks for `__call__` on the type, so it agrees with the builtin and is handy when the check sits in a chain of other ABC checks. One trap worth stating: `typing.Callable` is an **annotation**, not a runtime test. The bare form happens to work with `isinstance`, but the parameterized form everyone actually writes — `Callable[[str], int]` — raises `TypeError: Subscripted generics cannot be used with class and instance checks`. Static typing describes the call signature; it does not check it at runtime. ### The interview point The question is really about duck typing. Python's calling convention is a protocol, and the idiomatic way to test a protocol is to ask for the protocol, not to enumerate the concrete types that implement it. `callable` is the protocol test; `types.FunctionType` is one implementation of it. Choosing the type test is the same mistake as checking `isinstance(x, list)` when what you need is an iterable.

  • Does `callable(x)` returning True guarantee that `x(payload)` will not raise?
    No. It only says the object's type has a call implementation. The call can still fail on arity, on argument types, or because the body raises — and calling a class runs a full instantiation. Treat `callable` as a cheap rejection at the boundary and use `inspect.signature` when the number or names of parameters are part of the contract. The reliable direction is the negative one: `False` genuinely means the object cannot be called.
  • Why doesn't assigning `obj.__call__ = f` on an instance make `obj()` work?
    Implicit special-method lookup goes to the type, not the instance dictionary. The call syntax and `callable()` both consult `type(obj)`, so an instance attribute named `__call__` is simply ignored: `obj()` raises `TypeError: '...' object is not callable` and `callable(obj)` stays `False`. Define `__call__` in the class body to make instances callable; per-instance behaviour has to be data the shared `__call__` reads.
  • When would `inspect.isroutine` be a better test than `callable`?
    When you specifically need something function-like rather than merely callable — for example before touching function-only metadata, or when accepting a class as a handler would be a bug because it would construct an object instead of doing work. `inspect.isroutine` admits Python functions, lambdas, built-in functions, bound methods and method descriptors while excluding classes and arbitrary objects with `__call__`. `inspect.isfunction` narrows further to `def` and `lambda` alone.

callable(x) asks whether the object has a button you can press; types.FunctionType asks whether the button was made in one particular factory.

saying these in an interview costs you the question

  • Believes only def and lambda produce callable objects
  • Says classes are not callable in Python
  • Thinks isinstance(x, types.FunctionType) matches built-ins like len
  • Assumes a True from callable means the call will succeed
  • Sets __call__ on an instance and expects the instance to be callable
  • Uses parameterized typing.Callable as a runtime isinstance check

context