# Plum dispatches on type annotations, and the dispatch is positional

> Plum is a Python library for multiple dispatch, where a function is overloaded on the types of its arguments and the most specific matching implementation wins. It suits scientific Python code that has to pick the right implementation for arrays, floats and abstract quantities, and its one sharp rule is that dispatch only looks at positional arguments, so a parameter without a default must be passed positionally.

**beartype/plum** — Multiple dispatch in Python

- Repository: https://github.com/beartype/plum
- Website: https://beartype.github.io/plum
- Stars: 661 · Forks: 31
- Language: Python
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/beartype-plum

## Overloading a function on argument types

Plum describes itself as multiple dispatch in Python, and the design philosophy is stated plainly: to provide an implementation of multiple dispatch that is Pythonic, yet close to how Julia does it. The README links a comparison between Plum, multipledispatch and multimethod, which tells you the project positions itself against the two established Python options rather than ignoring them.

The mechanism is that you write one function several times, each with a different annotation, and Plum picks the implementation at call time. The example in the README is the smallest possible case:

```python
from numbers import Number

from plum import dispatch


@dispatch
def f(x: str):
    return "This is a string!"


@dispatch
def f(x: int):
    return "This is an integer!"


@dispatch
def f(x: Number):
    return "This is a number, but I don't know which type."
```

The annotation is the dispatch key, not a type hint. That is the difference from typing, and it is the whole point: the annotations decide which body runs.

The resolution follows specificity. A string matches the str implementation, an integer matches the int one, and a float, which is a Number but neither, falls through to the Number implementation, which is the fallback for anything numeric.

The package name on PyPI is plum-dispatch, Plum requires Python 3.10 or higher, and the install is a single pip command.

## When nothing matches, the error lists the closest candidates

The failure behaviour is where a dispatch library earns its keep, and Plum's is unusually informative. Calling the function with an object that matches none of the implementations does not produce a bare TypeError. The README shows what happens:

```python
>>> f(object())
NotFoundLookupError: `f(<object object at 0x7fd3b01cd330>)` could not be resolved.

Closest candidates are the following:
    f(x: str)
        <function f at 0x7fd400644ee0> @ /<ipython-input-2-c9f6cdbea9f3>:6
    f(x: int)
        <function f at 0x7fd3a0235ca0> @ /<ipython-input-2-c9f6cdbea9f3>:11
    f(x: numbers.Number)
        <function f at 0x7fd3a0235d30> @ /<ipython-input-2-c9f6cdbea9f3>:16
```

The exception is a NotFoundLookupError, and the message names the function, shows the arguments it could not resolve, and then lists every candidate with the source location of each implementation. For a library where the set of implementations is scattered across modules, that list is a map of the API.

It also means the failure is not silently swallowed. A call that hits no implementation raises rather than falling back to something arbitrary, which is the behaviour you want when a dispatch decision is choosing between numerically different code paths.

## The rule that catches people: dispatch reads positional arguments only

The README flags this with an IMPORTANT callout, and it is the single thing to internalise before writing any code. Dispatch as implemented by Plum is based on the positional arguments to a function. Keyword arguments are not used in the decision making for which method to call.

The practical consequence is that positional arguments without a default value must always be given as positional arguments. In the README's example, a function defined with an int annotation and called as f(1) works, while the same call written as f(x=1) raises a NotFoundLookupError reporting that the function could not be resolved.

So a call that works positionally raises NotFoundLookupError when the same argument is passed by keyword, because the dispatcher saw no annotated argument to dispatch on.

This is a design choice, and it is defensible. Deciding on keyword arguments would mean two calls to the same function with the same types could reach different implementations, which makes the behaviour of a function depend on the caller's syntax. The cost is that Plum does not fit a codebase that has settled on keyword-first calling conventions, and it is worth checking your call sites before you commit to it.

## Multi-argument dispatch picks the specialised pair, not the first match

The interesting case is dispatch over several arguments, where each position can be specialised independently. The README's multiplication example has a fallback for numbers and specialisations for reals and rationals:

```python
from numbers import Number, Real, Rational

from plum import dispatch


@dispatch
def multiply(x: Number, y: Number):
    return "Performing fallback implementation of multiplication..."


@dispatch
def multiply(x: Real, y: Real):
    return "Performing specialised implementation for reals..."


@dispatch
def multiply(x: Rational, y: Rational):
    return "Performing specialised implementation for rationals..."
```

The results show how the resolution works. Two integers, which are rationals, hit the Rational implementation. Two floats are reals but not rationals, so they hit the Real implementation. Two complex numbers are numbers and nothing more, so they fall back to the Number implementation.

The mixed call is the case worth noting. multiply(1, 1.0) is documented as automatically choosing the right optimisation, and it returns the specialised implementation for reals. In other words, Plum does not require both arguments to match the same implementation exactly. It finds the most specific combination available, so an integer paired with a float is treated as a real case.

That behaviour is the reason to reach for a dispatch library rather than writing a chain of isinstance checks. Determining the best available implementation across several arguments, and reporting the closest candidates when there is none, is the work Plum does.

## Beartype does the type checking, and the dependency floor moved

Plum 2 is powered by Beartype, and the README says so in a note, asking for issues if the new release misbehaves. The dependency list in pyproject.toml shows how that plays out in practice: beartype is required at 0.22.2 or newer for Python 3.14 and above, and at 0.16.2 or newer for earlier versions, alongside typing-extensions at 4.9.0 or newer and rich at 10.0 or newer.

That version split is a practical detail for anyone pinning dependencies. On a modern interpreter you get a much newer beartype than on 3.13, and the two branches do not have identical feature sets, so a lock file resolved on one Python version may not satisfy the other.

The rest of the manifest describes a project with serious development infrastructure. The build backend is hatchling with hatch-vcs for version derivation, and the version is dynamic, taken from the version control system. The package is typed, marks itself as Production/Stable, and declares itself operating system independent. The dependency groups cover docs with jupyter-book, lint with pre-commit and pylint, nox sessions, static tests with mypy and pyright, and runtime tests with pytest, pytest-cov, numpy, ipython, coveralls and sybil for documentation testing.

The presence of sybil is a nice signal. It means the examples in the documentation, including the interactive examples in the README, are executed as part of the test suite, so the code you copy from the docs is checked rather than merely written.

## Adoption in scientific Python and a licence you can read quickly

The README lists the projects using Plum, and the list is more informative than a star count. It includes NetKet, a machine learning library with JAX and Flax targeted at quantum physics, which uses Plum extensively to pick the right, efficient implementation for a large combination of interacting objects. It includes GeometricKernels, implementing kernels on non-Euclidean spaces such as Riemannian manifolds, graphs and meshes, and MMEval, a unified evaluation library for multiple machine learning libraries.

Several entries are about backend independence rather than dispatch for its own sake: LAB uses Plum to provide backend-agnostic linear algebra that works with PyTorch, TensorFlow, JAX and similar, Quax implements multiple dispatch over abstract array types in JAX, Unxt implements unitful quantities in JAX, and Varz provides backend-agnostic tools for non-linear optimisation. fasttransform provides the main building block of data pipelines in fastai.

The pattern is consistent. These are libraries where the same mathematical operation has several implementations depending on the argument types, and the type of the argument is the natural thing to dispatch on.

The licence is MIT, in a LICENCE.txt file, and the pyproject.toml declares it as MIT as well. The repository also carries a noxfile.py for task automation, a conftest.py for pytest configuration, a .pre-commit-config.yaml for hooks, a todo.tasks file, an uv.lock for the uv package manager, and a documentation directory with a .readthedocs.yaml. The release record shows 2.9.0 on 2026-04-28, 2.10.0 on 2026-09-03 and 2.10.1 on 2026-09-07, with the last push to master on 2026-10-07.

## Conclusion

Adopt Plum when your Python code has functions whose right implementation depends on the types of several arguments at once, which is common in scientific and machine learning libraries, since Plum resolves the most specific match and reports the closest candidates when nothing matches. Do not adopt it as a replacement for ordinary overloading or for keyword-driven APIs, because the decision is made on positional arguments only, and do not expect it to work in a codebase that passes arguments by keyword by default. Before you commit, check that your call sites can pass required arguments positionally, pin the Python floor at 3.10 or higher, read the comparison page against multipledispatch and multimethod, and note that Plum 2 is powered by Beartype, so the version of beartype in your environment matters, with 2.10.1 published on 2026-09-07 and the last push on 2026-10-07.

## FAQ

### How do I install Plum for multiple dispatch in Python?

Plum requires Python 3.10 or higher and is installed with pip install plum-dispatch. The package is named plum-dispatch on PyPI and the import is plum, and it depends on beartype, typing-extensions and rich.

### Why does my Plum dispatch call fail with NotFoundLookupError?

The most common cause is passing an argument by keyword. Dispatch in Plum is based on the positional arguments to a function, and keyword arguments are not used in the decision making, so positional arguments without a default value must always be given as positional arguments.

### How does Plum choose between multiple implementations of the same function?

It dispatches on the types of the positional arguments and selects the most specific matching implementation, with broader annotations acting as fallbacks. When several arguments are involved it finds the most specific available combination, so a call mixing an integer and a float can resolve to a real-valued implementation.

### How does Plum compare with multipledispatch and multimethod?

The README links a comparison page in the documentation covering Plum, multipledispatch and multimethod, and states the design goal of a Pythonic implementation that stays close to how Julia does it. The README also notes that Plum 2 is powered by Beartype.

### Which Python versions does Plum support and what does it depend on?

Plum requires Python 3.10 or higher. The dependencies are beartype, with a different minimum version depending on whether you are on Python 3.14 or earlier, typing-extensions and rich, and the project is licensed under MIT.

## Sources

- [beartype/plum on GitHub](https://github.com/beartype/plum)
- [License: MIT](https://github.com/beartype/plum/blob/master/LICENSE)
- [Project website](https://beartype.github.io/plum)
- [README](https://github.com/beartype/plum/blob/master/README.md)
- [Releases](https://github.com/beartype/plum/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/beartype-plum
