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

前端 未结 10 1556
太阳男子
太阳男子 2020-11-28 06:45

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

>&g         


        
相关标签:
10条回答
  • 2020-11-28 07:26

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

    0 讨论(0)
  • 2020-11-28 07:27

    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.

    0 讨论(0)
  • 2020-11-28 07:29

    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)
    
    0 讨论(0)
  • 2020-11-28 07:33

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

    0 讨论(0)
  • 2020-11-28 07:35

    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.

    0 讨论(0)
  • 2020-11-28 07:35

    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))
    
    0 讨论(0)
提交回复
热议问题