Can one partially apply the second argument of a function that takes no keyword arguments?

PythonArgumentsPartial Application

Python Problem Overview


Take for example the python built in pow() function.

xs = [1,2,3,4,5,6,7,8]

from functools import partial

list(map(partial(pow,2),xs))

>>> [2, 4, 8, 16, 32, 128, 256]

but how would I raise the xs to the power of 2?

to get [1, 4, 9, 16, 25, 49, 64]

list(map(partial(pow,y=2),xs))

TypeError: pow() takes no keyword arguments

I know list comprehensions would be easier.

Python Solutions


Solution 1 - Python

No

According to the documentation, partial cannot do this (emphasis my own):

> partial.args > > >The leftmost positional arguments that will be prepended to the positional arguments


You could always just "fix" pow to have keyword args:

_pow = pow
pow = lambda x, y: _pow(x, y)

Solution 2 - Python

I think I'd just use this simple one-liner:

import itertools
print list(itertools.imap(pow, [1, 2, 3], itertools.repeat(2)))

Update:

I also came up with a funnier than useful solution. It's a beautiful syntactic sugar, profiting from the fact that the ... literal means Ellipsis in Python3. It's a modified version of partial, allowing to omit some positional arguments between the leftmost and rightmost ones. The only drawback is that you can't pass anymore Ellipsis as argument.

import itertools
def partial(func, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = keywords.copy()
        newkeywords.update(fkeywords)
        return func(*(newfunc.leftmost_args + fargs + newfunc.rightmost_args), **newkeywords)
    newfunc.func = func
    args = iter(args)
    newfunc.leftmost_args = tuple(itertools.takewhile(lambda v: v != Ellipsis, args))
    newfunc.rightmost_args = tuple(args)
    newfunc.keywords = keywords
    return newfunc

>>> print partial(pow, ..., 2, 3)(5) # (5^2)%3
1
>>> print partial(pow, 2, ..., 3)(5) # (2^5)%3
2
>>> print partial(pow, 2, 3, ...)(5) # (2^3)%5
3
>>> print partial(pow, 2, 3)(5) # (2^3)%5
3

So the the solution for the original question would be with this version of partial list(map(partial(pow, ..., 2),xs))

Solution 3 - Python

Why not just create a quick lambda function which reorders the args and partial that

partial(lambda p, x: pow(x, p), 2)

Solution 4 - Python

You could create a helper function for this:

from functools import wraps
def foo(a, b, c, d, e):
    print('foo(a={}, b={}, c={}, d={}, e={})'.format(a, b, c, d, e))

def partial_at(func, index, value):
    @wraps(func)
    def result(*rest, **kwargs):
        args = []
        args.extend(rest[:index])
        args.append(value)
        args.extend(rest[index:])
        return func(*args, **kwargs)
    return result

if __name__ == '__main__':
    bar = partial_at(foo, 2, 'C')
    bar('A', 'B', 'D', 'E') 
    # Prints: foo(a=A, b=B, c=C, d=D, e=E)

Disclaimer: I haven't tested this with keyword arguments so it might blow up because of them somehow. Also I'm not sure if this is what @wraps should be used for but it seemed right -ish.

Solution 5 - Python

you could use a closure

xs = [1,2,3,4,5,6,7,8]

def closure(method, param):
  def t(x):
    return method(x, param)
  return t

f = closure(pow, 2)
f(10)
f = closure(pow, 3)
f(10)

Solution 6 - Python

You can do this with lambda, which is more flexible than functools.partial():

pow_two = lambda base: pow(base, 2)
print(pow_two(3))  # 9

More generally:

def bind_skip_first(func, *args, **kwargs):
  return lambda first: func(first, *args, **kwargs)

pow_two = bind_skip_first(pow, 2)
print(pow_two(3))  # 9

One down-side of lambda is that some libraries are not able to serialize it.

Solution 7 - Python

One way of doing it would be:

def testfunc1(xs):
    from functools import partial
    def mypow(x,y): return x ** y
    return list(map(partial(mypow,y=2),xs))

but this involves re-defining the pow function.

if the use of partial was not 'needed' then a simple lambda would do the trick

def testfunc2(xs):
    return list(map(lambda x: pow(x,2), xs))

And a specific way to map the pow of 2 would be

def testfunc5(xs):
    from operator import mul
    return list(map(mul,xs,xs))

but none of these fully address the problem directly of partial applicaton in relation to keyword arguments

Solution 8 - Python

As already said that's a limitation of functools.partial if the function you want to partial doesn't accept keyword arguments.

If you don't mind using an external library 1 you could use iteration_utilities.partial which has a partial that supports placeholders:

>>> from iteration_utilities import partial
>>> square = partial(pow, partial._, 2)  # the partial._ attribute represents a placeholder
>>> list(map(square, xs))
[1, 4, 9, 16, 25, 36, 49, 64]

1 Disclaimer: I'm the author of the iteration_utilities library (installation instructions can be found in the documentation in case you're interested).

Solution 9 - Python

The very versatile funcy includes an rpartial function that exactly addresses this problem.

xs = [1,2,3,4,5,6,7,8]
from funcy import rpartial
list(map(rpartial(pow, 2), xs))
# [1, 4, 9, 16, 25, 36, 49, 64]

It's just a lambda under the hood:

def rpartial(func, *args):
    """Partially applies last arguments."""
    return lambda *a: func(*(a + args))

Solution 10 - Python

Even though this question was already answered, you can get the results you're looking for with a recipe taken from itertools.repeat:

from itertools import repeat


xs = list(range(1, 9))  # [1, 2, 3, 4, 5, 6, 7, 8]
xs_pow_2 = list(map(pow, xs, repeat(2)))  # [1, 4, 9, 16, 25, 36, 49, 64]

Hopefully this helps someone.

Solution 11 - Python

If you can't use lambda functions, you can also write a simple wrapper function that reorders the arguments.

def _pow(y, x):
    return pow(x, y)

and then call

list(map(partial(_pow,2),xs))

>>> [1, 4, 9, 16, 25, 36, 49, 64]

Solution 12 - Python

Yes

if you created your partial class

class MyPartial:
    def __init__(self, func, *args):
        self._func = func
        self._args = args
        
    def __call__(self, *args):
        return self._func(*args, *self._args) # swap ordering
    
xs = [1,2,3,4,5,6,7,8]
list(map(MyPartial(pow,2),xs))

>>> [1, 4, 9, 16, 25, 36, 49, 64]

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionbeoliverView Question on Stackoverflow
Solution 1 - PythonEricView Answer on Stackoverflow
Solution 2 - PythonkosiiView Answer on Stackoverflow
Solution 3 - PythonnrobView Answer on Stackoverflow
Solution 4 - PythonmillimooseView Answer on Stackoverflow
Solution 5 - PythoniruvarView Answer on Stackoverflow
Solution 6 - PythondanijarView Answer on Stackoverflow
Solution 7 - PythonbeoliverView Answer on Stackoverflow
Solution 8 - PythonMSeifertView Answer on Stackoverflow
Solution 9 - PythonMicah SmithView Answer on Stackoverflow
Solution 10 - PythonMikeView Answer on Stackoverflow
Solution 11 - PythonCorvinceView Answer on Stackoverflow
Solution 12 - PythonRapheal KalandadzeView Answer on Stackoverflow