27

To understand *args and **kwargs I made some searchs about, when I fell on this question *args and **kwargs?

The answer below the chosen answer caught my attention, which is this:

class Foo(object):
    def __init__(self, value1, value2):
    # do something with the values
        print value1, value2

class MyFoo(Foo):
    def __init__(self, *args, **kwargs):
    # do something else, don't care about the args
        print 'myfoo'
        super(MyFoo, self).__init__(*args, **kwargs)

I tried some things on this example and running the code this way:

class Foo(object):
    def __init__(self, value1, value2):
    # do something with the values
        print 'I think something is being called here'
        print value1, value2


class MyFoo(Foo):
    def __init__(self, *args, **kwargs):
    # do something else, don't care about the args
        print args, kwargs
        super(MyFoo, self).__init__(*args, **kwargs)


foo = MyFoo('Python', 2.7, stack='overflow')

I got this:

[...]
    super(MyFoo, self).__init__(*args, **kwargs)
TypeError: __init__() got an unexpected keyword argument 'stack'

Changing to be like super(MyFoo, self).__init__(args, kwargs)

the results are:

('Python', 2.7) {'stack': 'overflow'}
I think something is being called here
('Python', 2.7) {'stack': 'overflow'}

For some blow mind reasons I'm questioning this: what could be right and wrong in the example above? What would be allowed to do and what wouldn't in real life production?

Community
  • 1
  • 1
Nice Guy
  • 479
  • 2
  • 6
  • 12

4 Answers4

28

Your Foo.__init__() does not support arbitrary keyword arguments. You can add **kw to it's signature to make it accept them:

class Foo(object):
    def __init__(self, value1, value2, **kw):
       print 'I think something is being called here'
       print value1, value2, kw

Keyword parameters are matched only with arguments with exact matching keyword names; your Foo method would need to have Python and stack keyword parameters. If no matching keyword parameter are found but a **kw parameter is, they are collected in that parameter instead.

If your subclass knows that the parent class only has positional arguments, you can always pass in positionals:

class MyFoo(Foo):
    def __init__(self, *args, **kwargs):
    # do something else, don't care about the args
        print args, kwargs
        while len(args) < 2:
            args += kwargs.popitem()
        super(MyFoo, self).__init__(*args[:2])

where you now must pass in two or more arguments to MyFoo for the call to work.

In essence, super().methodname returns a reference to the bound method; from there on out it is a normal method, so you need to pass in arguments that any method can accept. If your method doesn't accept keyword arguments, you get an exception.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • @NiceGuy: Yes, that'd be the expected output. I am not certain what your second sentence means. – Martijn Pieters Feb 09 '14 at 15:26
  • I got the idea! I was equivocated in the last sentence from the first comment. – Nice Guy Feb 09 '14 at 15:28
  • @MartijnPieters is it possible to have base class with constructor of the type `def __init__(self,*args,**kwargs):` and then inherit this class in a child class , where the following is used `def __init__(self,*args,**kwargs): super().__init__(*args,**kwargs)` – Alexander Cska Oct 06 '18 at 18:59
  • @AlexanderCska: Yes, that's possible, but I'm not sure what the point is then of having both the base and the subclass use `*args, **kwargs` if neither of them are using any arguments. – Martijn Pieters Oct 06 '18 at 19:10
  • @MartijnPieters I pasted only small part, to fit in the comment.I am sorry for this inconvenience. Basically I have 4 arguments (a,b,c,d). The base class does `self.a=a self.b=b` and the child class uses the other two. The problem is that python can't find the variables if they are implicitly passed. `class base(object): def __init__(self,*args,**kwargs): self.a=a self.b=b class child(base): def __init__(self,*args,**kwargs): super().__init__(*args,**kwargs) self.c=c self.d=d` I don't know how to properly format that. Sorry – Alexander Cska Oct 06 '18 at 20:04
  • @AlexanderCska: nothing is passed implicitly; just name your arguments in the signature: `def __init__(self, a=None, b=None, *args, **kwargs):` and `def __init__(self, c=None, d=Nore, *args, **kwargs):` – Martijn Pieters Oct 06 '18 at 22:11
  • @MartijnPieters exactly, this is a bit of long logic on my side. The constructor kind of gets a value, but don't know the variable name itself. Therefore, i would do either implicit arguments, as you suggested, or would use dictionary via kwargs. I was thinking, that by using *args, **kwargs the names of the variables are also known. Which is not the case. Than you. – Alexander Cska Oct 07 '18 at 10:00
8

When you do this:

super(MyFoo, self).__init__(*args, **kwargs)

It is the same as if you did this, base on how your code is working:

super(MyFoo, self).__init__("python", 2.7, stack="overflow")

However, the __init__ function of Foo (from which MyFoo inherits) doesn't support a keyword argument named "stack".

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685
5

The reason is all the arguments are already unpacked into kwargs and it is a dict now. and you are trying to pass it to a normal variables.

def bun(args,kwargs):
print 'i am here'
print kwargs

def fun(*args,**kwargs):
print kwargs
bun(*args,**kwargs)

 fun(hill=3,bi=9) # will fail.


def bun(*args,**kwargs):
print 'i am here'
print kwargs

def fun(*args,**kwargs):
print kwargs
bun(*args,**kwargs) # will work.



fun(hill=3,bi=9)

Try making the modification at

class Foo(object):
    def __init__(self, *value1, **value2):
# do something with the values
        print 'I think something is being called here'
        print value1, value2


class MyFoo(Foo):
    def __init__(self, *args, **kwargs):
# do something else, don't care about the args
        print args, kwargs
        super(MyFoo, self).__init__(*args, **kwargs)


foo = MyFoo('Python', 2.7, stack='overflow'

should work..!

Vasif
  • 1,393
  • 10
  • 26
2

I think it is worth adding that this can be used to simplify the __init__ signatures in the child classes. The positional arguments are pealed off from left to right so if you add them to the front and pass the rest to args and kwargs you can avoid mistakes from forgetting to add them explicitly to each of the children. There is some discussion about if that is an acceptable exception "explicit is better than implicit" here. For long lists of args in deep hierarchy this may be clearer and easier to maintain.

To modify this example, I add not_for_Foo to the front of MyFoo and pass the rest through super.

class Foo(object):
    def __init__(self, a_value1, a_value2, a_stack=None, *args, **kwargs):
        """do something with the values"""
        super(Foo, self).__init__(*args, **kwargs) # to objects constructor fwiw, but object.__init__() takes no args
        self.value1 = a_value1
        self.value2 = a_value2
        self.stack = a_stack
        return

    def __str__(self):
        return ', '.join(['%s: %s' % (k, v) for k, v in self.__dict__.items()])


class MyFoo(Foo):
    def __init__(self, not_for_Foo, *args, **kwargs):
        # do something else, don't care about the args
        super(MyFoo, self).__init__(*args, **kwargs)
        self.not_for_Foo = not_for_Foo # peals off
        self.myvalue1 = 'my_' + self.value1 # already set by super


if __name__ == '__main__':

    print 'Foo with args'
    foo = Foo('Python', 2.7, 'my stack')
    print foo

    print '\nMyFoo with kwargs'
    myfoo = MyFoo('my not for foo', value2=2.7, value1='Python', stack='my other stack')
    print myfoo


$ python argsNkwargs.py 
Foo with args
value2: 2.7, value1: Python, stack: my stack

MyFoo with kwargs
myvalue1: my_Python, not_for_Foo: my not for foo, value2: 2.7, value1: 
Python, stack: my other stack

-lrm

  • Helpful because it verifies my assumptions of how to add new child class constructors that need to pass default args, but not interfere with the default arg default-values by passing as **kwarg, my trouble is that child classes are limited to adding args on the front not the back, since any args they want to modify when they call super need explicitly replacing in the **kwargs dictionary, not the *args list. Explicitly must modify kwargs, since the child class has no obvious way of knowing what the default-value in the parent was when they do this? –  Jun 21 '21 at 09:45