skip to content

What does Callable[[int], str] mean, and how does Callable[..., str] differ?

level: juniorimportance: should knowfreq 46%

answer

  1. Two slots, not one
  2. The first slot is a list
  3. Ellipsis means parameters unchecked
  4. Positional only; no keywords expressible
  5. Import from collections.abc since 3.9

basics

~10 s

Callable[[int], str] describes a function taking exactly one int parameter and returning str. Callable[..., str] pins only the return type: the literal ellipsis means the parameter list is unchecked, so any signature is accepted.

solid answer

~40 s

`Callable` takes two subscript slots: a **list** of parameter types and a single return type. `Callable[[int], str]` is a function of one `int` returning `str`; `Callable[[], None]` takes nothing; `Callable[[int, str], bool]` takes two positional parameters. The literal `...` in the first slot — `Callable[..., str]` — is a deliberate escape hatch that says "any parameters at all, but the result is `str`"; a checker will then accept any call, so it trades away argument checking. That form is right for genuinely opaque callbacks and factories, and wrong as a lazy default. The bracketed form can only express positional parameters: it cannot describe keyword, optional, or defaulted parameters, for which you write a `typing.Protocol` with a `__call__` method or reach for `ParamSpec`. On modern Python, import `Callable` from `collections.abc`, not from `typing`.

code

python · 10 lines
python
from collections.abc import Callable

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

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

print(apply_twice(lambda n: n + 1, 3))
print(any_signature(lambda: "ok"))

go deeper

for a junior

Recall the shape: parameter types in a list first, return type second, and the ellipsis form when the parameters are unknown. Being able to write Callable[[int], str] correctly from memory is the whole bar here.

for a middle

Explain that the ellipsis switches off argument checking rather than describing arguments, that only positional parameters can appear in the list, and that collections.abc is the right import since 3.9.

for a senior

Show judgment about where the ellipsis is honest and where it is a hole. In review, be ready to point at Callable[..., Any] on a decorator and name the concrete bug class it hides from callers.

for a principal

Own the codebase policy: which boundaries may stay loosely typed, whether a Protocol with __call__ is the house style for keyword-taking callbacks, and how strict the checker settings should be for higher-order code.

## The two slots `Callable` is a generic with exactly two subscript positions: `Callable[<parameters>, <return>]`. The first position is a **list** of parameter types, written with real square brackets; the second is a single type for the result. The bracket-inside-bracket shape trips people up constantly, so read it aloud: `Callable[[int], str]` is *"list of parameters: one int; returns: str"*. Concrete forms: ```python Callable[[], None] # takes nothing, returns None Callable[[int], str] # one int parameter, returns str Callable[[int, str], bool] # two positional parameters Callable[..., str] # any parameters whatsoever, returns str ``` A frequent beginner error is `Callable[int, str]` without the inner brackets. That is not a valid annotation and a type checker rejects it; at runtime it may even build an object without complaint, which is exactly why the mistake survives to review. ## What the ellipsis actually means The `...` in `Callable[..., str]` is the real `Ellipsis` object used as a literal token, and it means *the parameter list is unspecified*. It is not "zero or more arguments of any type" in a checked sense — it is an instruction to the checker to stop checking arguments at that call. Any call, with any number of positional or keyword arguments, is accepted; only the `str` result is enforced. That makes it a **gradual-typing escape hatch**, and it has honest uses: a plugin registry that stores heterogeneous factories, a callback whose signature is genuinely decided by the caller, a boundary to third-party code you cannot describe. It is the wrong tool when you *do* know the signature, and it is especially wrong on decorators — a decorator typed `Callable[..., Any] -> Callable[..., Any]` silently erases the signature of every function it wraps, which is the problem `ParamSpec` exists to solve. ## What the bracketed form cannot say The parameter list describes **positional parameters only**. There is no syntax inside `Callable[...]` for: * a keyword parameter (`f(x, *, verbose=False)`), * an optional or defaulted parameter, * `*args` / `**kwargs` forwarding. When you need those, the two answers are a `typing.Protocol` class with a `__call__` method — which spells out a full signature, names and defaults included — or `ParamSpec`, when the signature is not fixed but *copied* from somewhere else. ## Where to import it from Since Python 3.9 (PEP 585) the abstract base classes are subscriptable directly, so the modern import is `from collections.abc import Callable`. `typing.Callable` still works and is an alias, but it has been deprecated since 3.9 and linters flag it in new code. Note the asymmetry that catches people: `collections.abc.Callable` is what you *annotate* with, while the builtin `callable(obj)` is what you *test* with at runtime, and `isinstance(obj, collections.abc.Callable)` only checks that the object defines `__call__` — it can never verify a signature. No runtime check in Python validates that a function matches `Callable[[int], str]`; that is a static claim only. ## Anything callable satisfies it `Callable` is not limited to functions defined with `def`. A lambda, a `functools.partial`, a bound method, a class (calling a class returns an instance, so a class object is `Callable[..., Instance]`), and any object with a `__call__` method all qualify. This is why `Callable[..., MyClass]` is the idiomatic annotation for a factory parameter that may receive either the class itself or a function that builds one. ## In return position and in containers `Callable` is not confined to parameters. A factory helper is annotated `-> Callable[[int], str]` when it builds and returns a function, and a registry of handlers is `dict[str, Callable[[bytes], None]]`. The registry case is where the explicit list pays for itself most visibly: every handler registered into that mapping is checked against one agreed shape, so a handler that takes the wrong argument type is caught where it is registered rather than where it is eventually dispatched. At runtime, subscripting produces a generic alias object rather than anything Python enforces — `collections.abc.Callable[[int], str]` simply evaluates to a description. Since Python 3.14, annotations are not even evaluated when the function is defined unless something asks for them (PEP 649), so an annotation naming a type that does not exist yet no longer raises at definition time. ## Reading it in review Two habits make callable annotations pay off. First, prefer the explicit parameter list wherever the signature is known — it is the only form that catches a caller passing arguments in the wrong order. Second, treat every `Callable[..., X]` in a diff as a question: is the signature genuinely unknowable here, or did someone reach for the ellipsis because writing the list was inconvenient? In a typed codebase the ellipsis is a small hole in the roof, and holes cluster around exactly the higher-order code where mistakes are hardest to spot by eye.

  • How would you type a callback that takes a keyword argument with a default?
    Not with `Callable` — its parameter list is positional-only. Define a `typing.Protocol` with a `__call__` method that spells out the full signature, including parameter names, keyword-only markers and defaults. Checkers then match any function whose signature is compatible with that `__call__`, and callers get real checking on keyword arguments.
  • Does isinstance against collections.abc.Callable verify the parameter types?
    No. It only checks that the object defines `__call__`, so any function, lambda, bound method or callable instance passes regardless of its signature. Subscripted forms cannot be used in `isinstance` at all. Signature agreement is a purely static claim — nothing at runtime enforces `Callable[[int], str]`.
  • Is a class object a valid Callable?
    Yes. Calling a class constructs an instance, so a class works as `Callable[..., Instance]`, and with a known `__init__` it matches an explicit list such as `Callable[[int], Invoice]`. That is why factory parameters are usually annotated `Callable[..., Thing]`: it accepts both the class itself and any function returning one.

The bracketed list is a job description that names each duty in order; the ellipsis version only promises what the worker hands back at the end, and says nothing about what you must give them.

saying these in an interview costs you the question

  • Writes Callable[int, str] without the inner brackets
  • Thinks Callable[..., str] checks the arguments
  • Claims isinstance can verify a callable's signature
  • Uses typing.Callable in new code, unaware of collections.abc
  • Believes only def functions satisfy Callable
  • Tries to express keyword parameters inside the bracket list

context