Skip to content
← All Python articles

Python explainer

Preset Arguments with partial and partialmethod

About 4 min to read. Allow extra time to work through examples.

Make a new callable with some arguments filled in, put presets on a class with partialmethod and staticmethod, and type a retry wrapper.

This article uses Python. You can make it your practice mode to browse related content across the site.

You call label(text, mark="!!!") in a dozen places, always with the same mark. The functools.partial function makes a new callable with that argument already filled in:

from functools import partial

def label(text: str, *, mark: str = "!") -> str:
    return text.upper() + mark

banner = partial(label, mark="!!!")
print(banner("ready"))                        # READY!!!
print(banner("ready", mark="?"))              # READY?
print(banner.func is label, banner.keywords)  # True {'mark': '!!!'}

Calling banner("ready") calls label("ready", mark="!!!"). A keyword you pass at call time replaces the stored one, and stored positional arguments go before the ones you pass. The partial keeps what it stored in .func, .args and .keywords, so you can inspect it when you debug.

mypy checks the arguments you give partial, too. For example, partial(label, mark=3) is an error, because mark must be a str.

A partial can be pickled

Pickling turns an object into bytes, so that you can save it to a file or send it to another process. The parallelism track uses it to send work to other processes. A partial pickles if its function and its stored arguments do:

import pickle
from functools import partial

upper = partial(str.upper, "revived")
restored = pickle.loads(pickle.dumps(upper))
print(restored())  # REVIVED

Python pickles a function by its name, so that the process that loads it can import it again. A lambda has no name to import, so pickling one fails. Where work may go to another process, prefer a named function or a partial to a lambda. The exercise checks both cases.

Put presets on a class

When you store a partial on a class, say whether it should receive self:

from functools import partial, partialmethod

def shout(text: str, *, mark: str = "!") -> str:
    return text.upper() + mark

class Notifier:
    def __init__(self, channel: str) -> None:
        self.channel = channel

    def send(self, body: str, *, level: str = "info") -> str:
        return f"{self.channel}/{level}: {body}"

    alert = partialmethod(send, level="alert")
    banner = staticmethod(partial(shout, mark="!!!"))

notifier = Notifier("ops")
print(notifier.alert("disk full"))  # ops/alert: disk full
print(notifier.banner("ship it"))   # SHIP IT!!!
print(Notifier.banner("ship it"))   # SHIP IT!!!
  • A partialmethod makes a method. Like any method, alert receives the instance as self, then the preset level.
  • A staticmethod around a partial stops Python from passing self, so banner receives only the caller's arguments, through an instance or the class.

The wrappers also protect you from a change in Python 3.14. Before 3.14, a bare partial on a class never received self. From 3.14, it receives self when you call it through an instance, like a method. So in this exercise's starter, the bare alert = partial(send, level="alert") raises TypeError on Python 3.12 and works on 3.14, and the bare banner does the opposite. Wrapped in partialmethod or staticmethod, both behave the same on every version.

Keep a retry wrapper's types

The exercise asks for make_retrier, which wraps a function and calls it again after a failure. It uses the pattern from "Write Typed Decorators with wraps and ParamSpec". The inner function takes *args: P.args, **kwargs: P.kwargs, and make_retrier returns Callable[P, R]. So mypy checks every call to the retrier against the original function's parameters and return type. The starter's Callable[..., Any] lets any call through and makes every result Any.

The retrier also takes a sleep function instead of calling time.sleep itself.

Check your understanding Why does make_retrier take a sleep function instead of calling time.sleep? Show answer

So that a test can pass waits.append. It records each delay and returns at once, so the test checks the delays without waiting for them. Real code passes time.sleep. Retry only errors that may go away on a second try, such as a timeout, and do not sleep after the last attempt. Retrying also repeats side effects, so a retried call that sends an email can send it twice.

Going further

Python 3.14 added functools.Placeholder, which leaves a gap among the stored positional arguments for the caller to fill. The exercise supports Python 3.12, so it does not use it.

step 33 of 55

Everyday Standard Library Tools

Up next Retry a Flaky Call and Preset Arguments with partial