Python super() pass in *args

Viewed 119

I am confused about the convention of passing *args in super().__init__() in python inheritance. I understand the need to use keyword arguments **kwargs so the required arguments can be taken by the class in CRO if needed, but why there's also a *args?

Example: Suppose Sneaky is used as part of a multiple inheritance class structure such as:

class Sneaky:
   def __init__(self, sneaky = false, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.sneaky = sneaky

class Person:
    def __init__(self, human = false,  *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.human = human

class Thief(Sneaky, Person): 
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

t = Thief(human = true, sneaky = true)
print(t.human)
# True

So what if we have below instead aka remove the *args?

class Sneaky:
   def __init__(self, sneaky = false, **kwargs):
        super().__init__( **kwargs)
        self.sneaky = sneaky

class Person:
    def __init__(self, human = false, **kwargs):
        super().__init__(**kwargs)
        self.human = human

class Thief(Sneaky, Person): 
    def __init__(self,  **kwargs):
        super().__init__( **kwargs)

t = Thief(human = true, sneaky = true)
print(t.human)
# True
2 Answers

Passing in *args means that you can initialize your Thief like

>> x = Thief(True, False)
>> x.human
False
>> x.sneaky
True

This is confusing to read, and difficult to trace with multiple inheritance, but it could be useful. Maintaining this capability means you could do

sneaky = (True, False, True,)
human = (True, False, False,)
thieves = [Thief(*args) for args in zip(sneaky, human)]

This is a bit contrived, but I think it illustrates why you might want to allow positional arguments.

If you do want to remove the *args and not support positional arguments, you can exclude them from Sneaky and Person as well by adding *, after self,.

class Sneaky:

    def __init__(self, *, sneaky=False, **kwargs):
        super().__init__(**kwargs)
        self.sneaky = sneaky

class Person:

    def __init__(self, *, human=False, **kwargs):
        super().__init__(**kwargs)
        self.human = human

This does not put all positional arguments in an un-named *. This will raise a TypeError if you try to provide a positional arugment.

In first case following code will output True.

t = Thief(True, True)
print(t.human)
# True

But in second (without *args) - it will raise exception:

t = Thief(True, True)  # arguments without keywords
print(t.human)
# raises
# TypeError: __init__() takes 1 positional argument but 3 were given
Related