14

I have a Python class with a method which should accept arguments and keyword arguments this way

class plot:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def set_axis(self, *args, xlabel="x", ylabel="y", **kwargs):
        for arg in args:
            <do something>
        for key in kwargs:
             <do somethng else>

when calling:

plt = plot(x, y)
plt.set_axis("test1", "test2", xlabel="new_x", my_kwarg="test3")

I get the error: TypeError: set_axis() got multiple values for keyword argument 'xlabel'

Anyway if I set my method like

class plot:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def set_axis(self, xlabel="x", ylabel="y", *args, **kwargs):
        for arg in args:
            <do something>
        for key in kwargs:
             <do somethng else>

and call:

plt = plot(x, y)
plt.set_axis(xlabel="new_x", "test1", "test2", my_kwarg="test3")

I get SyntaxError: non-keyword arg after keyword arg, as I was expecting. What is wrong with the first case? How should I tell my method to accept any user argument and keyword argument, other than the default ones? (Hope my question is clear enough)

sophros
  • 14,672
  • 11
  • 46
  • 75
fmonegaglia
  • 2,749
  • 2
  • 24
  • 34
  • 2
    What version of Python are you using? The first class you list doesn't parse in Python 2.7.3 because `*args` is succeeded by a parameter name in the `set_axis` definition. – phant0m Dec 22 '12 at 15:56

3 Answers3

16

In Python 3 this works:

Python 3.2.3 (default, Oct 19 2012, 19:53:16) 
>>> def set_axis(self, *args, xlabel="x", ylabel="y", **kwargs):
...     print(args, xlabel, ylabel, kwargs)
... 
>>> set_axis(None, "test1", "test2", xlabel="new_x", my_kwarg="test3")
('test1', 'test2') new_x y {'my_kwarg': 'test3'}
>>> 
warvariuc
  • 57,116
  • 41
  • 173
  • 227
14

You would use a different pattern:

def set_axis(self, *args, **kwargs):
    xlabel = kwargs.get('xlabel', 'x')
    ylabel = kwargs.get('ylabel', 'y')

This allows you to use * and ** while keeping the fallback values if keyword arguments aren't defined.

Jure C.
  • 3,013
  • 28
  • 33
  • How? It's either defined in kwargs and it reads its value, or it uses a fallback 'x' one. – Jure C. Dec 22 '12 at 16:39
  • 5
    Is this the only way to accomplish this? I feel as if you shouldn't have to revert to having all keyword args in **kwargs in order to mix *args and named arguments – eipxen May 13 '14 at 18:51
4

Here's a slight tweek to Jure C.'s answer:

def set_axis(self, *args, **kwargs):
    xlabel = kwargs.pop('xlabel', 'x')
    ylabel = kwargs.pop('ylabel', 'y')

I changed get to pop to remove xlabel and ylabel from kwargs if present. I did this because the rest of the code in the original question contains a loop that is meant to iterate through all kwargs except for xlabel and ylabel.

MarredCheese
  • 17,541
  • 8
  • 92
  • 91