41

So I am trying to understand partial:

import functools

def f(x,y) :
    print x+y

g0 = functools.partial( f, 3 )
g0(1)

4 # Works as expected

In:

g1 = functools.partial( f, y=3 )
g1(1)

4 # Works as expected

In:

g2 = functools.partial( f, x=3 )
g2(1)

TypeError: f() got multiple values for keyword argument 'x'

The TypeError disappears if I use y as a keyword argument:

In:

g2( y=1 )

4

What causes the TypeError?

duplode
  • 33,731
  • 7
  • 79
  • 150
usual me
  • 8,338
  • 10
  • 52
  • 95

3 Answers3

51

This has nothing to do with functools.partial, really. You are essentially calling your function like this:

f(1, x=3)

Python first fulfils the positional arguments, and your first argument is x. Then the keyword arguments are applied, and you again supplied x.

functools.partial() has no means to detect that you already supplied the first positional argument as a keyword argument instead. It will not augment your call by replacing the positional argument with a y= keyword argument.

When mixing positional and keyword arguments, you must take care not to use the same argument twice.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 1
    Is there a way to fill in the default parameters in an arbitrary fashion then, to leave a function with only one parameter that can be as deep as necessary within the args/kwargs chain? – chiffa May 31 '18 at 20:52
  • @chiffa: I'm not sure what you mean. You can't just pass arbitrary arguments to a function unless the function has `*args` or `**kwargs` catch-all parameters. If a function has keyword parameters (`name=some_default`) then you don't have to provide anything for those, they already have a default value if you omit them. – Martijn Pieters Jun 01 '18 at 11:56
  • I realized that was the problem in my case. I was trying to perform plot of a function with a number of parameters where one set of parameters varied always in the same way, whereas some other parameters needed to be changed to draw contrasting plots and to simplify writing those plots I tried to generate a partial function that was automatically providing the parameters that were not changing. It worked, but I needed to provide parameters to the partial function as `my_partial_function(param_name=param)`. – chiffa Jun 03 '18 at 14:33
1

To expand on @Martijn-Pieters answer, this is how you can preserve the positional nature of the second parameter. Here, the argument to g2 is passed positionally as y:

def f(x,y) :
    print x+y

g2 = functools.partial( f, *[3] )
g2(1)

That works when we're trying to replace an initial set of the arguments of f. I don't know how to use partial to replace e.g. just the second argument of a 3-parameter function, and allow the first and third to be passed positionally. But you could do that with a lambda expression.

nealmcb
  • 12,479
  • 7
  • 66
  • 91
0

Not an answer, a follow up question (since I can't add a comment without 50 reputation), could you please elaborate or use simple terms as the answers here are above my comprehension. I have a similar issue using a button in Maya

import maya.cmds as cmds
from functools import partial

class Myclass(object):
    def __init__(self):
        pass
    def createui(self):
        derp = cmds.window()
        cmds.formLayout()
        cmds.button(label = "w/e", c = partial(self.f, x = 3))
        cmds.showWindow(derp)
    def f(self, x = 5, y = 3, *_):
        print(x+y)

herp = Myclass()
herp.createui()

clicking on the button will give that same error "got multiple values for keyword argument 'x'"

even though what I thought was being sent as arguments was a call to the class, some stupid call to the UI element itself (usually a value of False) which should be put in the *_ catch all argument, and then keyword argument of x.

rasonage
  • 53
  • 1
  • 1
  • 9
  • 2
    You should open up a new question and reference to this one if it is helpful. It helps keep things clean and searchable. – Catsy May 15 '20 at 15:43