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

13

71

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.

Slightly answered 23/6, 2012 at 22:58 Comment(2)
another usage of partial starting from 2-nd argument is partial for method(s) omitting self argumentUthrop
for methods you can use: def meth(cls, self,...) and then partial(meth, cls)Debi
A
71

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)
Ampliate answered 23/6, 2012 at 23:29 Comment(7)
if in python 4.X pow() is "fixed", we will all know where they got their idea from :)Slightly
@beoliver: Actually, as of PEP-457, positional-only arguments are formalized into what seems to be a feature, so I would never expect a fix to powAmpliate
When using lambda here, you don't need the functools.partial() anymore.Cobaltic
What is this doing? The arguments have not been changed at all: i would have anticipated a reversal of the order of x,y -> y,xPiecework
Following up on my comment: should not one or other of lambda x,y and _pow(x,y) have the x,y reversed??Piecework
@javadba: No, the context of that line is to '"fix" pow to have keyword args". Once you've done that, the question is no longer relevant, as pow is no longer "a function that takes no keyword arguments". The OPs code that fails above works if preceded with this "fix".Ampliate
honestly partial could've been perfect if they just let you submit a tuple of tuples of the form (arg_index, value). Why didn't they just include that to kill the problem once and for allTersina
H
23

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

partial(lambda p, x: pow(x, p), 2)
Holguin answered 26/5, 2016 at 18:12 Comment(2)
I just found that method useful when defining a property (using property() function directly, rather than as a decorator), where I needed to default the second argument to a __setitem__() call.Polity
At that point, why partial it? Why not f = lambda p, x=2: pow(x, p). Or even f = lambda p: pow(2, p)Mokpo
D
20

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))

Dramatic answered 23/6, 2012 at 23:43 Comment(5)
nice, for some reason I never use repeat. and as I'm on 3.X, it's just list(map(pow, [1, 2, 3], itertools.repeat(2)))Slightly
nice, I didn't know they changed map in Python3Dramatic
The funny one is pretty clever in a somewhat horrifying way :P (Also, you're not really supposed to pass Ellipsis around to begin with, so that's not much of a disadvantage. Unless you count "using Ellipsis for something it's not meant to be used" a disadvantage.)Giorgia
after learning some functional programming, for me it looks like a bizarre way of function currying in pythonDramatic
@kosii That's exactly what it is. It's impressive that Python is flexible enough to facilitate many of these functional patterns.Saturn
G
8

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.

Giorgia answered 23/6, 2012 at 23:10 Comment(1)
Major +1. I was tempted to select this answer, but I suppose I was thinking more about the inbuilt functools.partial. But this is definitely being saved. I like :)Slightly
N
6

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)
Nuke answered 23/6, 2012 at 23:16 Comment(0)
C
4

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.

Cobaltic answered 27/2, 2018 at 18:57 Comment(3)
What do you mean with some libraries? I think no library is able to serialize them.Margartmargate
I'm often using ruamel.yaml and it serializes lambdas fine.Cobaltic
Okay, I was thinking about the built-in modules. Thanks for the clarification :)Margartmargate
S
2

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

Slightly answered 23/6, 2012 at 22:59 Comment(0)
C
2

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.

Cornelius answered 28/12, 2020 at 18:17 Comment(1)
beautiful :). Just did something similar on our side with dict: def _remove_table_id(ds: List[Dict[str, Any]]) -> List[Dict[str, Any]]: return list(map(dict.pop, ds, repeat('table_id')))Pichardo
P
2

Yes, you can do it, provided the function takes keyword arguments. You just need to know the name.

In the case of pow() (provided you are using Python 3.8 or newer) you need exp instead of y.

Try to do:

xs = [1,2,3,4,5,6,7,8]
print(list(map(partial(pow,exp=2),xs)))
Prettypretty answered 29/9, 2022 at 15:42 Comment(0)
M
1

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).

Margartmargate answered 24/6, 2017 at 10:0 Comment(0)
M
1

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))
Merwin answered 13/8, 2018 at 19:32 Comment(1)
Not "exactly". The second argument need not necessarily be the last one.Gruchot
L
0

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]
Lewallen answered 14/9, 2018 at 13:3 Comment(0)
Q
0

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]
Qintar answered 26/8, 2021 at 20:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.