69

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.

beoliver
  • 5,579
  • 5
  • 36
  • 72

13 Answers13

69

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)
Community
  • 1
  • 1
Eric
  • 95,302
  • 53
  • 242
  • 374
  • 1
    if in python 4.X pow() is "fixed", we will all know where they got their idea from :) – beoliver Jun 23 '12 at 23:58
  • @beoliver: Actually, as of [PEP-457](https://www.python.org/dev/peps/pep-0457/), positional-only arguments are formalized into what seems to be a feature, so I would never expect a fix to `pow` – Eric Jul 26 '17 at 12:24
  • 6
    When using lambda here, you don't need the `functools.partial()` anymore. – danijar Feb 27 '18 at 19:01
  • 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,x – WestCoastProjects Oct 22 '19 at 20:28
  • Following up on my comment: should not one or other of `lambda x,y` and `_pow(x,y)` have the `x,y` reversed?? – WestCoastProjects Oct 23 '19 at 14:13
  • @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". – Eric Oct 23 '19 at 16:38
  • 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 all – Sidharth Ghoshal Nov 06 '22 at 19:24
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))

mutantacule
  • 6,913
  • 1
  • 25
  • 39
  • 2
    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)))` – beoliver Jun 23 '12 at 23:53
  • nice, I didn't know they changed `map` in Python3 – mutantacule Jun 24 '12 at 00:26
  • 1
    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.) – millimoose Jun 25 '12 at 22:57
  • 2
    after learning some functional programming, for me it looks like a bizarre way of function currying in python – mutantacule May 23 '14 at 20:12
  • @kosii That's exactly what it is. It's impressive that Python is flexible enough to facilitate many of these functional patterns. – josiah Sep 27 '18 at 16:04
20

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

partial(lambda p, x: pow(x, p), 2)
nrob
  • 861
  • 1
  • 8
  • 22
  • 1
    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. – Eric Smith Aug 08 '19 at 23:08
  • 6
    At that point, why partial it? Why not f = lambda p, x=2: pow(x, p). Or even f = lambda p: pow(2, p) – jobermark Dec 16 '20 at 22:40
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.

millimoose
  • 39,073
  • 9
  • 82
  • 134
  • 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 :) – beoliver Jun 23 '12 at 23:41
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)
iruvar
  • 22,736
  • 7
  • 53
  • 82
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.

danijar
  • 32,406
  • 45
  • 166
  • 297
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

beoliver
  • 5,579
  • 5
  • 36
  • 72
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.

Mike
  • 1,080
  • 1
  • 9
  • 25
  • 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')))` – amohr May 19 '22 at 03:13
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)))
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
yossi
  • 31
  • 2
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).

MSeifert
  • 145,886
  • 38
  • 333
  • 352
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))
Micah Smith
  • 4,203
  • 22
  • 28
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]
Corvince
  • 161
  • 1
  • 8
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]