No history yet

Callable and Ellipsis

Typing Functions

You already know how to add type hints to simple variables, function parameters, and return values like strings and integers. But what happens when the value itself is a function? In Python, functions are first-class citizens, meaning they can be passed as arguments to other functions, just like any other object.

This is common in patterns where you want to make a function's behavior customizable. For example, a function might apply a specific operation to some data, where the operation itself is provided as an argument. To type-hint this, we need a way to describe the function's signature: its arguments and its return value.

For this, we use Callable from the typing module.

from typing import Callable

def add(a: int, b: int) -> int:
    return a + b

def subtract(a: int, b: int) -> int:
    return a - b

# This function accepts another function as its 'operation' argument
def apply_operation(x: int, y: int, operation: Callable[[int, int], int]) -> int:
    """Applies a given binary integer operation to x and y."""
    return operation(x, y)

result_add = apply_operation(10, 5, add)
result_sub = apply_operation(10, 5, subtract)

print(f"Addition result: {result_add}")
print(f"Subtraction result: {result_sub}")

Let's break down the type hint for the operation parameter:

Callable[[int,int],int]Callable[[int, int], int]

This hint tells type checkers and other developers that apply_operation expects a function that takes two integers and returns one integer. Both add and subtract match this signature perfectly.

Returning Callables

Callable isn't just for parameters. It can also describe the return value of a function. This is useful when writing functions that generate and return other functions, a concept often used in decorators and functional programming.

Imagine a function that creates a customized multiplier. You give it a number, and it gives you back a new function that will multiply any input by that number.

from typing import Callable

def create_multiplier(factor: float) -> Callable[[float], float]:
    """Returns a new function that multiplies its input by 'factor'."""
    def multiplier(x: float) -> float:
        return x * factor
    return multiplier

# Create a function that doubles a number
doubler = create_multiplier(2.0)

# Create a function that triples a number
tripler = create_multiplier(3.0)

print(f"Doubling 5.5 gives: {doubler(5.5)}")
print(f"Tripling 10 gives: {tripler(10.0)}")

The return type hint Callable[[float], float] clearly states that create_multiplier doesn't return a number, but a new function. This returned function takes a single float as an argument and, in turn, returns a float. This makes the code's intent much clearer than if the return type was left ambiguous.

Handling Flexible Arguments

Sometimes you need to specify that an argument is callable, but you don't know or don't need to enforce the specific signature of the arguments it accepts. For instance, you might want to log when a function is called, regardless of what arguments that function takes.

For these situations, you can use an ellipsis (...).

Ellipsis

noun

A literal constant in Python represented by three dots (...). In type hinting, it serves as a placeholder for an unspecified number or type of arguments.

When used inside Callable, the ellipsis replaces the list of argument types. This tells the type checker that any arguments are acceptable.

from typing import Callable, Any

def run_and_log(func: Callable[..., Any], *args, **kwargs) -> Any:
    """Logs a call to a function and then executes it."""
    print(f"Calling function '{func.__name__}' with arguments...")
    result = func(*args, **kwargs)
    print(f"'{func.__name__}' returned: {result}")
    return result

def greet(name: str) -> str:
    return f"Hello, {name}!"

def calculate_sum(x: int, y: int, z: int) -> int:
    return x + y + z

# Using the logger with different functions
run_and_log(greet, name="Alice")
run_and_log(calculate_sum, 1, 2, 3)

The type hint Callable[..., Any] means:

  • ...: The function can accept any number of positional or keyword arguments of any type.
  • Any: The function can return a value of any type.

This provides the flexibility to pass any function to run_and_log while still making it clear that func is expected to be a callable object, not an integer or a string.

Quiz Questions 1/5

What is the primary purpose of Callable from Python's typing module?

Quiz Questions 2/5

How would you correctly type hint a parameter func that expects a function taking a string and an integer, and returning a boolean?

With Callable and Ellipsis, you can now precisely document functions that operate on other functions, a key pattern in writing clean, reusable, and extensible Python code.