partialmethod and singledispatchmethod

functools · Advanced · 9 min

Classes often need configured variants of a method or type-based dispatch on an argument.

Plain partial and singledispatch don't understand methods: they don't bind self. functools provides method-aware versions.

partialmethod creates a method with some arguments pre-filled; self is bound normally:

class Light:
    def set_state(self, on): ...
    turn_on = partialmethod(set_state, True)

Assigning a plain partial object as a class attribute does not bind self today, so obj.attr() calls the function without the instance. In 3.13 this raises a FutureWarning, because partial will become a method descriptor in a future release. Use partialmethod for methods, or wrap the partial in staticmethod to keep the non-binding behaviour explicitly.

singledispatchmethod dispatches on the type of the first argument after self (or cls):

class Formatter:
    @singledispatchmethod
    def format(self, value): ...
    @format.register
    def _(self, value: int): ...

It also works with @classmethod when stacked correctly: @singledispatchmethod on the outside, @classmethod on the inside, both for the base and for registered implementations.

These descriptors return bound callables on attribute access, so introspection and help() work as for ordinary methods.

Example

import warnings
from functools import partial, partialmethod, singledispatchmethod

class Light:
    on = False
    def set_state(self, on): self.on = on
    turn_on = partialmethod(set_state, True)
light = Light(); light.turn_on(); print(light.on)   # True
class Formatter:
    @singledispatchmethod
    def format(self, value):
        return str(value)
    @format.register
    def _(self, value: float):
        return f"{value:.2f}"
    @format.register
    def _(self, value: list):
        return "[" + "; ".join(self.format(v) for v in value) + "]"
print(Formatter().format([1, 2.5, "x"]))   # [1; 2.50; x]
class Bad:
    hello = partial(print, "hi")      # not bound to instances
with warnings.catch_warnings(record=True) as caught:
    warnings.simplefilter("always")
    Bad().hello                       # attribute access warns in 3.13
print(caught[0].category.__name__)    # FutureWarning

Remember

  • partialmethod pre-fills arguments and binds self like a normal method
  • A plain partial class attribute doesn't bind self and warns in 3.13
  • singledispatchmethod dispatches on the first argument after self

Watch out for

  • Assigning partial(...) as a class attribute and expecting it to receive self.

Check yourself

  1. Inside a class, which correctly creates a method enable(self) that calls self.set(True)?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/library/functools.html#functools.partialmethod