Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

**kwargs and default arguments

class Klass:
  def __init__(self, **kwargs):
    self.func(variable=True, **kwargs)

  def func(self, variable=True, **kwargs):
    print(variable)


if __name__ == '__main__':
  Klass(variable=False)

I was wondering why I am getting TypeError: func() got multiple values for keyword argument 'variable'.

I am thinking it should print False because I override the default value of variable to False and pass kwargs along the way.

like image 606
Zizheng Wu Avatar asked Oct 20 '25 04:10

Zizheng Wu


2 Answers

You can't pass the same argument twice, and variable=True, **kwargs does exactly that when kwargs contains a key for variable; in this case, you made the call effectively self.func(variable=True, variable=False) which is clearly wrong. Assuming you can't receive variable as a separate argument, e.g.:

def __init__(self, variable=True, **kwargs):
    self.func(variable, **kwargs)

# On Python 3, you can easily keep variable keyword-only with:
def __init__(self, *, variable=True, **kwargs):
    self.func(variable, **kwargs)

# while the Python 2 equivalent for keyword-only args is rather nastier:
def __init__(self, *positional_forbidden, variable=True, **kwargs):
    if positional_forbidden:
        raise TypeError("__init__ takes 1 positional argument but {} were given".format(len(positional_forbidden)+1))
    self.func(variable, **kwargs)

then the other approach is to set the default in the kwargs dict itself:

def __init__(self, **kwargs):
    kwargs.setdefault('variable', True)  # Sets variable to True only if not passed by caller
    self.func(**kwargs)

In Python 3.5, with PEP 448's additional unpacking generalizations, you could one-line this safely as:

def __init__(self, **kwargs):
    self.func(**{'variable': True, **kwargs})

because repeated keys are legal when creating a new dict (only the last occurrence of a key is kept), so you can create a brand new dict with unique mappings, then immediately unpack it.

like image 166
ShadowRanger Avatar answered Oct 22 '25 19:10

ShadowRanger


When you declare an object of class Klass,

Klass(variable=False)

the variable is parsed as an keyword argument (kwargs), which is a dict anyway. So inside the __init__ method, you can find the kwargs being like

{'variable': False'}

And in

self.func(variable=True, **kwargs)

the double asterisk implies to parse everything in a dict as keyword arguments. Technically this line is equal to

self.func(variable=True, **{'variable': False'})

and equal to

self.func(variable=True, variable=False)

And that's why you got a TypeError.

Here's is probably what you are looking for:

class Klass:
  def __init__(self, **kwargs):
    self.func(**kwargs)

  def func(self, variable=True, **kwargs):
    print(variable)

if __name__ == '__main__':
  Klass(variable=False)
like image 25
K.Marker Avatar answered Oct 22 '25 19:10

K.Marker