multipledispatch

Type-based multiple dispatch for Python, resolving which function to call from the types of all positional arguments, not just the first.

Library
PyPI
v1.0.0
847stars
BSD 3-Clause License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
36/100Needs Attention
Development Activity0
Maintenance0
Community56
Maturity60
Momentum28

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
74/100Good
Architecture78
Code Quality72
Innovation80
Learning Curve65

multipledispatch is a small, dependency-free Python library that implements multiple dispatch: instead of overloading a function based on only its first argument (as functools.singledispatch does), it resolves the correct implementation to call by examining the types of every positional argument. Implementations are registered with a @dispatch(TypeA, TypeB, ...) decorator, and the library builds an internal signature table per function name, automatically ordering and caching lookups for fast repeated calls.

Beyond the basic case, it supports inheritance-aware matching, union-type signatures (e.g. (int, float)), variadic signatures for arbitrary-arity dispatch, isolated namespaces so library authors can avoid polluting a shared global dispatch table, and dispatch on instance methods inside classes. A distinguishing feature is eager ambiguity detection: if two registered signatures could both plausibly match the same call, a warning is raised at definition time with a concrete suggested signature to resolve it, rather than surfacing confusing behavior only when the ambiguous call actually happens.

What You Get

  • A dispatch(*types) decorator that registers a function implementation against a full positional-argument type signature
  • Automatic, cached resolution of the correct implementation at call time via a topologically-sorted signature ordering
  • Eager ambiguity detection that warns at registration time (with a suggested resolving signature) instead of failing silently at call time
  • Union-type signatures (e.g. (int, float)) so one implementation can match several argument types
  • Variadic signature support ([int] or Variadic[int]) for functions that accept an arbitrary number of same-typed arguments
  • Isolated namespace= dictionaries so library authors can scope their dispatch tables instead of sharing the global one
  • Transparent support for dispatch inside class bodies via MethodDispatcher, including automatic method detection

Common Use Cases

  • Numeric/type-driven overloading - defining add(x, y) differently for (int, int) vs (object, object) without manual isinstance branching
  • Scientific/array libraries - dispatching operations differently depending on combinations of array, scalar, or symbolic types
  • Visitor-pattern style code - implementing operations over a small closed set of related types (e.g. AST nodes) as separate, clearly-typed functions instead of one large conditional
  • Library-internal type coercion - converting or combining values only when their concrete type pairing is supported, with typed errors otherwise
  • Namespaced plugin systems - using namespace= to keep a library’s own multiple-dispatch table isolated from the caller’s global dispatch table

Under The Hood

Architecture The library is organized into five small modules: core.py (the public dispatch() decorator and global_namespace), dispatcher.py (the Dispatcher/MethodDispatcher classes handling registration, caching, and the __call__ resolution path), conflict.py (signature ordering and ambiguity detection via supercedes/consistent/ambiguous and a topological sort), variadic.py (a VariadicSignatureType metaclass enabling [int]-style variadic signatures), and utils.py (generic toposort/groupby/expand_tuples helpers, explicitly attributed in comments to theano and toolz to avoid licensing issues). dispatch() inspects the decorated function’s name and, unless it detects an instance method (via ismethod() introspecting the first parameter), registers it against a namespace dict of Dispatcher instances keyed by function name; each Dispatcher.add() call updates a funcs signature table and invalidates a cached ordering, while Dispatcher.__call__ types the incoming arguments, checks a per-call-site cache, and falls back to dispatch()/dispatch_iter(), which walks the ordering computed in conflict.py to find the most specific matching implementation. Anything that depends on funcs, the cached ordering, or the pickling contract (__getstate__/__setstate__) would break if this core Dispatcher abstraction changed.

Tech Stack Pure Python with zero runtime dependencies, packaged with classic setuptools (setup.py + setup.cfg, no pyproject.toml — predating PEP 517/518 adoption). Dev tooling is defined entirely through .pre-commit-config.yaml: black, flake8 with flake8-bugbear, isort, pyupgrade, absolufy-imports, and codespell for the docs. Two GitHub Actions workflows run pre-commit hooks and a test matrix. Sphinx powers the docs/ directory. No database, web framework, or non-trivial build step — the deployment target is a plain sdist/wheel published to PyPI.

Code Quality Tests live under multipledispatch/tests/ across six files covering dispatcher registration/resolution, ambiguity/ordering logic, variadic signatures, and a benchmarking harness, all written as plain pytest-style assert functions rather than unittest.TestCase classes. Error handling is explicit and typed: TypeError for malformed or non-type signature entries, NotImplementedError (plus a custom MDNotImplementedError for opt-out fallthrough) for unmatched signatures, and DeprecationWarning/AmbiguityWarning surfaced through the standard warnings module rather than being swallowed. Naming is consistent snake_case throughout; type hints are largely absent, reflecting the library’s age, and style is enforced through black/flake8/isort in CI rather than embedded in the library itself.

API Design dispatch() is a single decorator that infers argument types purely from call-site positional types, with no schema or registration boilerplate beyond stacking @dispatch(...) per overload — including automatic detection of instance methods so it works unmodified inside class bodies, a namespace= escape hatch for isolated dispatch tables, and a variadic story ([int]/Variadic[...]) for arbitrary-arity dispatch. Ambiguity between overlapping signatures is caught eagerly at decoration time with an actionable suggested resolving signature printed in the warning text, which is notably more helpful than implementations that only fail at call time. The learning curve is low: the README’s examples are idiomatic and the entire public surface is one decorator plus an optional keyword argument.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search