Support for using partial with positional only argument functions

I’d like to propose adding a way for functions that take positional only arguments to be used with functools.partial.

As an example, say you want to create a function that parses datetimes for a specific format.

Due to positional only arguments, the following will not work:

>>> functools.partial(datetime.datetime.strptime, format="%d %B, %Y")
>>> p("3 June, 2021")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: strptime() takes no keyword arguments

I’d like to add a PLACEHOLDER (similar to c++ bind) sentinel in partial that allows to use the functools.partial for positional only argument functions. Example:

from functools import partial
p = partial(
    "%d %B, %Y"
p("3 June, 2019")  # works as expected

With the introduction of PEP570, there are now even more functions that cannot be used with partiality due to the enforcement of positional arguments, making this even more useful IMHO.

I have a PoC here if you want to play with it for the Python part. If this is of interest I’ll create a bpo, add this to the extension as well and send a PR.

1 Like

You can use lambda:

p = lambda x: datetime.datetime.strptime(x, "%d %B, %Y")
1 Like

That is indeed what I am doing right now :blush:, I guess it is also that I generally prefer partials over lambdas for this kind of case as I have seen people fall into cases like:

from functools import partial
import datetime
format_ = "%d %B, %Y"
p = lambda datestr: datetime.datetime.strptime(datestr, format_)
format_ = None
print(p("3 June, 2019"))   # Fails

Also, 100% subjective, it’s easier for me to read partials, as I know it is just the same functionality just binding arguments.
If lambdas were a perfect solution for binding arguments there would not be partials at all, right?


Rather than adding a magical “PLACEHOLDER” sentinel value, another
common solution is to define a version of partial that binds from the
right rather than the left.

Here’s an untested implementation:

def partial_right(func, /, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = {**keywords, **fkeywords}
        return func(*fargs, *args, **newkeywords)
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc

It is a one line change from the code given in the docs:

I don’t think this is a very common need. As far as I can tell, none of
the major functional-programming languages that support partial
application provide the functionality. Although that might be because
their partial application are built on currying?

In any case, you are not the only one to request this sort of feature,
at least in the Javascript world:

1 Like