I donât think thatâs correct. It has, but one thatâs consistent with Pythonâs rules about positional vs. keyword arguments. (Namely, that you canât mix them out of order, and positional args have to come first.) What partial doesnât do is give you a pass to break those rules.
You missed several more possibilities, though, and the rules for which of them you can and canât use inform the semantics of partial as well.
These are OK:
foo(b=4, a=3)
foo(3, b=4)
These are not:
foo(4, a=3)
foo(b=4, 3)
The second is a SyntaxError (âpositional argument follows keyword argumentâ).
The first is a TypeError, and as it turns out, the same as your g = partial(foo, a=3); g(4) because positional arguments always come first.
A partial with a keyword argument supplied doesnât slot that argument in as the first positional argument, it slots it in as the first keyword argument.
Thatâs also what makes this perfectly OK. It doesnât violate the ordering rules. (It only seems like it does, until you understand whatâs going on.):
>>> h = partial(foo, b=4)
>>> h(3)
That works because itâs equivalent to foo(3, b=4), NOT foo(b=4, 3). Which is why g = partial(foo, a=3) => g(4) => foo(4, a=3) breaks the rules.
(And, returning to your original claim, with h = partial(foo, b=4) you can choose between h(3) and h(a=3).)
âŚI suppose I canât disagree that this behavior is ânot obviousâ, but not everything in the language will be. And it is documented (emphasis added):
functools.partial(func, /, *args, **keywords)
Return a new partial object which when called will behave like func called with the positional arguments args and keyword arguments keywords. If more arguments are supplied to the call, they are appended to args. If additional keyword arguments are supplied, they extend and override keywords.