How to fill specific positional arguments with partial in python?

Question:

Basically, what I’d like to do is:

>>> from functools import partial
>>> partial(str.startswith, prefix='a')('a')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: startswith() takes no keyword arguments

But more generally the question is, how to fill specific positional arguments with partial.

P.S. I do realize that I can use a lambda instead.

Asked By: newtover

||

Answers:

It cannot be done. You have to make a wrapper function.

Ostensibly, you would use keyword arguments, as you tried to do – that’s what they’re for, right? Unfortunately, as you’ve discovered, python’s standard library functions do not take named parameters. Thus, it is not possible given the current implementation of partial without making another function to run interference.

According to the acceptance of PEP 309, what was accepted for inclusion was "a partial() type constructor binding leftmost positional arguments and any keywords." Furthermore, it states:

Note that when the object is called as though it were a function, positional arguments are appended to those provided to the constructor, and keyword arguments override and augment those provided to the constructor.

Positional arguments, keyword arguments or both can be supplied at when creating the object and when calling it.

Because additional positional arguments are appended, there would be no way to supply some preceding positional argument (by this implementation).

However, it goes on:

Partially applying arguments from the right, or inserting arguments at arbitrary positions creates its own problems, but pending discovery of a good implementation and non-confusing semantics, I don’t think it should be ruled out.

So, it apparently could be on the table, but as it stands, it is not implemented that way.

For the sake of disclosure, emphasis in quotes above was my own.

Answered By: Nate

Use this code:

# See: functoolspartial for binding...

class Function(object):
    def __init__(self, fn):
        self.fn = fn

    def __call__(self, *args, **kwargs):
        return self.fn(*args, **kwargs)

    def __add__(self, other):
        def subfn(*args, **kwargs):
            return self(*args, **kwargs) + other(*args, **kwargs)
        return subfn


class arg(object):
    """Tagging class"""
    def __init__(self, index):
        self.index = index


class bind(Function):
    """Reorder positional arguments.
    Eg: g = f('yp', _1, 17, _0, dp=23)
    Then g('a', 'b', another=55) --> f('yp', 'b', 17, 'a', dp=23, another=55)
    """

    def __init__(self, fn, *pargs, **pkwargs):
        # Maximum index referred to by the user.
        # Inputs to f above this index will be passed through
        self.fn = fn
        self.pargs = pargs
        self.pkwargs = pkwargs
        self.max_gindex = max(
            (x.index if isinstance(x, arg) else -1 for x in pargs),
            default=-1)

    def __call__(self, *gargs, **gkwargs):
        fargs = 
            [gargs[x.index] if isinstance(x, arg) else x for x in self.pargs] + 
            list(gargs[self.max_gindex+1:])

        fkwargs = dict(self.pkwargs)
        fkwargs.update(gkwargs)    # Overwrite keys
        return self.fn(*fargs, *fkwargs)

Then try it out like:

def myfn(a,b,c):
print(a,b,c)

bind(myfn, arg(1), 17, arg(0))(19,14)
Answered By: Elizabeth F

If you really need this you can use rpartial from funcy 3rd-party library.

Its code is here:

def rpartial(func, *args):
    return lambda *a: func(*(a + args))

So, your case can be handled as following:

>>> startswith_a = rpartial(str.startswith, 'a')
>>> startswith_a('abc')
True
>>> startswith_a('def')
False
Answered By: oblalex

I know this is old, but I run into this all the time, and I think I’ve created a neat solution. I use a slightly modified version of partial that uses an Ellipses object (...) as a placeholder value if you do not know it at the time that the object is built. It’s quite useful for exactly this!

Here is the original __call__ method of partial

def __call__(self, /, *args, **keywords):
    keywords = {**self.keywords, **keywords}
    return self.func(*self.args, *args, **keywords)

Instead, we can use the literal ... as a special case to indicate a placeholder value

>>> type(...)
<class 'ellipsis'>

Here’s the entire implementation:

class bind(partial):
    """
    An improved version of partial which accepts Ellipsis (...) as a placeholder
    """
    def __call__(self, *args, **keywords):
        keywords = {**self.keywords, **keywords}
        iargs = iter(args)
        args = (next(iargs) if arg is ... else arg for arg in self.args)
        return self.func(*args, *iargs, **keywords)

Usage is fairly straightforward

def foo(a, b, c, /, *, d):
    print(f"A({a}) B({b}) C({c}) D({d})")

f1 = bind(foo, 1, 2, 3, d=4)
f1()

f2 = bind(foo, 1, 2, d=4)
f2(3)

f3 = bind(foo, 1, ..., 3, d=4)
f3(2)

f4 = bind(foo, ..., 2, ..., d=4)
f4(1, 3)

f5 = bind(foo, ..., d=5)
f5(1, 2, 3, d=4)
Answered By: Goodies

There are probably tons of libs having fairly easy-to-use functions for this and maz is one you could use.

import maz
def add(x,y): 
    return x+y
add_two = maz.pospartial(add, [(1,2)]) # fixing argument 1 with value 2
add_two(3) # >>> 5
Answered By: Rikard Olsson
Categories: questions Tags:
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.