Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The *args and **kwds in python super call

Tags:

python

I am trying to understand the use of *args and **kwds when creating subclasses in Python.

I want to understand why this code behaves the way it does. If I leave out the *args and **kwds in a call to super().__init__, I get some strange argument unpacking.

Here is my test case:

class Animal(object):
    def __init__(self, moves, num_legs):
        self.moves = moves
        self.num_legs = num_legs

    def describe(self):
        print "Moves :{} , num_legs : {}".format(self.moves, self.num_legs)

class Snake(Animal):
    def __init__(self, poisonous, *args, **kwds):
        self.poisonous = poisonous
        print "I am poisonous:{}".format(self.poisonous)
        # This next line is key. You have to use *args , **kwds.
        # But here I have deliberately used the incorrect form,
        # `args` and `kwds`, and am suprised at what it does.
        super(Snake, self).__init__(args, kwds)

Now, when I create instances of the Snake subclass, which contains the erroneous call to super(…).__init__ (where I use args and kwds instead of *args and **kwds), I get some interesting “argument unpacking”.

s1 = Snake(False, moves=True, num_legs=0)
s2 = Snake(poisonous=False, moves=True, num_legs=1)
s3 = Snake(False, True, 3)
s1.describe()
s2.describe()
s3.describe()

What I get is:

Moves :() , num_legs : {'moves': True, 'num_legs': 0}
Moves :() , num_legs : {'moves': True, 'num_legs': 1}
Moves :(True, 3) , num_legs : {}

So why is it that in s1 and s2, __init__ assumes that moves = True and num_legs = 0 or 1 are keyword arguments, and sets the num_legs to a dict?

In s3, it unpacks both of the variables to moves (in class Animal) as a tuple.


I stumbled into this as I was trying to understand argument unpacking. Sorry in advance—I don't know how to frame this question any better.

like image 623
harijay Avatar asked Sep 11 '13 21:09

harijay


2 Answers

In Snake.__init__, args is a tuple of all positional arguments after poisonous and kwds is a dict of all the keyword arguments apart from poisonous. By calling

super(Snake,self).__init__(args,kwds)

you assign args to moves and kwds to num_legs in Animal.__init__. That’s exactly what you are seeing in your output.

The first two calls don’t have any positional arguments apart from poisonous, so args and consequential moves is an empty tuple. The third call has no keyword arguments, so kwds and consequential num_legs is an empty dict.

like image 182
Chronial Avatar answered Oct 07 '22 16:10

Chronial


In short: def __init__(self,poisonous,*args,**kwds): means: capture positional arguments in a tuple args and keyword arguments in a dictionary kwds. Similarly, super(Snake,self).__init__(*args, **kwds) means: unpack the tuple args and the dictionary kwds into arguments so that they're passed separately to __init__.

If you don't use the * and ** then you're passing args and kwds as they are, which means you're getting a tuple and a dictionary.


As you've said, you'd need to write:

super(Snake,self).__init__(*args, **kwds)

to properly pack / unpack the arguments. In your current code you're not packing / unpacking the arguments so it sets num_legs to a dictionary as that's what kwds is at the moment.


If you don't give the arguments names then they're positional arguments. Hence Snake(False,True,3) are all positional arguments.

If you do give the arguments names then they're keyword arguments: Snake(poisonous=False,moves=True,num_legs=1).

In the first case you're combining both one positional argument and two keyword arguments: Snake(False,moves=True,num_legs=0).

like image 28
Simeon Visser Avatar answered Oct 07 '22 15:10

Simeon Visser