In Python 2, attaching a functools.partial to a class produced an attribute that never bound self. Functions become methods through the descriptor protocol—__get__ runs on attribute access and inserts the instance—and partial objects didn’t implement it. The partial application worked; the method binding was the missing half. My Stack Overflow answer supplied the binding by hand:
from functools import update_wrapper
from functools import partial
from types import MethodType
class Basic(object):
def add(self, **kwds):
print sum(kwds.values())
Basic.add_to_one = MethodType(
update_wrapper(partial(Basic.add, a=1), Basic.add),
None,
Basic,
)x.add(a=1, b=9) and x.add_to_one(b=9) both print 10.
The load-bearing piece is the three-argument form MethodType(func, None, Basic): it manufactures an unbound method from any callable, and unbound methods carry the binding machinery that partial lacks. On attribute access the instance slides in as the first positional argument, the partial forwards it to Basic.add as self, and the frozen a=1 rides along. update_wrapper copies the original function’s metadata onto the partial so the manufactured method still introspects as add.
The recipe freezes keyword arguments only. partial prepends frozen positionals, which would put them before the instance inserted by MethodType. The wrapped function would then receive the frozen value as self and the instance as an extra argument.
The version boundary is why this is a retrospective. Python 3 removed unbound methods, and with them the three-argument MethodType—the recipe does not port. Python 3.4 then shipped the stdlib fix for that era: functools.partialmethod, a partial that implements the descriptor protocol itself, so add_to_one = partialmethod(add, a=1) in the class body binds and calls correctly. It did not absorb the recipe’s update_wrapper step. The class-level callable reports __name__ == "_method", and the bound partial has no __name__ at all. The stdlib replacement covers partial application and method binding, not metadata preservation.
Python 3.14 moved the boundary again: plain partial is now a method descriptor. For this keyword-only recipe, update_wrapper(partial(Basic.add, a=1), Basic.add) can be assigned directly to the class; it binds on instance access and keeps __name__ == "add". partialmethod still has a distinct job for frozen positional arguments and wrapped descriptors: it arranges for self before stored positional values and delegates binding to an underlying descriptor, while plain partial preserves its ordinary prefix order.