alancalvitti
alancalvitti

Reputation: 476

How to specify arg position for functool partial()

As per manual, functools partial() is 'used for partial function application which “freezes” some portion of a function’s arguments and/or keywords resulting in a new object with a simplified signature.'

What's the best way to specify the positions of the arguments that one wishes to evaluate?

EDIT

Note as per comments, the function to be partially evaluated may contain named and unnamed arguments (these functions should be completely arbitrary and may be preexisting)

END EDIT

For example, consider:

def f(x,y,z):
    return x + 2*y + 3*z 

Then, using

from functools import partial

both

partial(f,4)(5,6)

and

partial(f,4,5)(6)

give 32.

But what if one wants to evaluate, say the third argument z or the first and third arguments x, and z?

Is there a convenient way to pass the position information to partial, using a decorator or a dict whose keys are the desired arg positions and the respective values are the arg values? eg to pass the x and z positions something like like this:

partial_dict(f,{0:4,2:6})(5)

Upvotes: 6

Views: 2700

Answers (4)

Hrishikesh
Hrishikesh

Reputation: 1183

If you are okay with using lambda functions, you can also do something like

from functools import partial

def f(x, y, z):
    return x + 2 * y + 3 * z 
    
g = lambda y: partial(f, x=4, z=10)(y=y)

print(g(1))  # 36
print(g(2))  # 38

Upvotes: 0

blhsing
blhsing

Reputation: 106543

For easier usage, you can create a new object specifically to specify a positional argument that is to be skipped when sequentially listing values for positional arguments to be frozen with partial:

SKIP = object()

def partial_positionals(func, *positionals, **keywords):
    def wrapper(*args, **kwargs):
        arg = iter(args)
        return func(*(*(next(arg) if i is SKIP else i for i in positionals), *arg),
            **{**keywords, **kwargs})
    return wrapper

so that:

def f(x, y, z):
    return x + 2 * y + 3 * z

print(partial_positionals(f, 4, SKIP, 6)(5))

outputs:

32

Upvotes: 0

blhsing
blhsing

Reputation: 106543

No, partial is not designed to freeze positional arguments at non-sequential positions.

To achieve the desired behavior outlined in your question, you would have to come up with a wrapper function of your own like this:

def partial_positionals(func, positionals, **keywords):
    def wrapper(*args, **kwargs):
        arg = iter(args)
        return func(*(positionals[i] if i in positionals else next(arg)
            for i in range(len(args) + len(positionals))), **{**keywords, **kwargs})
    return wrapper

so that:

def f(x, y, z):
    return x + 2 * y + 3 * z

print(partial_positionals(f, {0: 4, 2: 6})(5))

outputs:

32

Upvotes: 4

Brian61354270
Brian61354270

Reputation: 14423

Simply use keyword arguments. Using your definition of f above,

>>> g = partial(f, z=10)
>>> g(2, 4)
40
>>> h = partial(f, y=4, z=10)
>>> h(2)
40

Note that once you use a keyword argument for a given parameter, you must use keyword arguments for all remaining arguments. For example, the following would not be valid:

>>> j = partial(f, x=2, z=10)
>>> j(4)
TypeError: f() got multiple values for argument 'x'

But continuing to use keyword arguments is:

>>> j = partial(f, x=2, z=10)
>>> j(y=4)
40

When you use functools.partial, you store the values of *args and **kwargs for later interpolation. When you later call the "partially applied" function, the implementation of functools.partial effectively adds the previously provided *args and **kwargs to the argument list at the front and end, respectively, as though you had inserted these argument-unpackings yourself. I.e., calling

h = partial(1, z=10)
f(4)

is roughly equivalent to writing

args = [1]
kwargs = {'z': 10}
f(*args, 4, **kwargs)

As such, the semantics of how you provide arguments to functools.partial is the same as how you would need to store arguments in the args and kwargs variables above such that the final call to f is sensible. For more information, take a look at the pseduo-implementation of functools.partial given in the functools module documentation

Upvotes: 2

Related Questions