gbronner
gbronner

Reputation: 1945

Python __init__ and varargs

According to the python documentation, __init__ can accept varargs:

object.__init__(self[, ...])

Called after the instance has been created (by __new__()), but before it is returned to the caller. The arguments are those passed to the class constructor expression. If a base class has an __init__() method, the derived class’s __init__() method, if any, must explicitly call it to ensure proper initialization of the base class part of the instance; for example:

BaseClass.__init__(self, [args...]).

I'm struggling to understand how to use varargs on classes that directly derive from object. In particular, when trying to instantiate a class that calls super(<classname>, self).__init__(*args, **kwargs), I find that if I instantiate the class with no arguments, everything is ok.

However, if I pass arguments to the init function, I get a the error:

super(A,self).__init__(*args,**kwargs)
TypeError: object.__init__() takes no parameters

It is my understanding that object.__init__ should be able to take parameters based on the documentation -- it also makes writing code easier, as every class can pass its arguments up the class hierarchy.

Is the documentation incorrect, or is object.__init__ a special case?

Code is below:

class A(object):
    def __init__(self, *args, **kwargs):
        for i,a in enumerate(args):
            print "arg", i,a
        for k,v in kwargs.iteritems():
            print "kwarg", k,v

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




a=A()
print "Done with first one"
a2=A(5,4,5,3)

Upvotes: 2

Views: 3079

Answers (3)

Mad Physicist
Mad Physicist

Reputation: 114478

There is a problem with your interpretation of the docs. In your first line object.__init__(self[, ...]) is not referring to varargs. It is just saying that there may be arguments other than self to __init__.

__init__ is not different from any other method in terms of how it is called, by super, or any other means. For a less confusing example, take something like abs. If you pass more than one argument to abs, it will raise TypeError: abs() takes exactly one argument (2 given). This is normal and expected. If you pass more arguments to an __init__ method than it expects, you will get the same error.

A Python method that is not stated to be able to accept varargs will not be able to accept arbitrary arguments. object.__init__ accepts no arguments. You can all it from your __init__ as either object.__init__(self) or super(type(self), self).__init__().

You can rewrite your example to look like this:

class A(object):
    def __init__(self, *args, **kwargs):
        for i, a in enumerate(args):
            print "arg", i, a
        for k, v in kwargs.iteritems():
            print "kwarg", k, v

        super(A, self).__init__()

This looks pointless at first, since you are extending a class that requires no initialization (object). However, even this simple example shows that you can process some of the arguments for your class, and pass others to the base class.

A common idiom is to explicitly name all the child class's arguments (both positional and keyword-only), and let the parent constructor deal with the remainder. For example, consider the following:

class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age

class Citizen(Person):
    def __init__(self, nationality, *args, **kwargs):
        self.nationality = nationality
        super(Citizen, self).__init__(*args, **kwargs)

Declaring Citizen to accept varargs in addition to the normal arguments is very convenient in this case. You don't have to remember what arguments Person accepts, or modify anything if they change. It does not mean that you can call Citizen.__init__ with completely arbitrary arguments, however. You still have to pass in a total of three arguments, and keywords can only have the names. nationality, name and age. Notice that Person.__init__ does not even bother to call object.__init__.

The important thing to keep in mind is that all the real classes you will be dealing with, whether from the Python libraries or external sources, should be well documented enough to tell you what you can and can't pass in. Just because something technically accepts varargs, does not mean that it does not have restrictions on what can be passed in.

Upvotes: 4

Torben Klein
Torben Klein

Reputation: 3136

super() is defective by design IMHO. See here: https://fuhm.net/super-harmful/

In short:

  • it interoperates badly with the old calling style
  • you must never forget it
  • you cannot change method signature on subclasses
  • you cannot even be sure in what order the methods will be called
  • multiple-inheritance is a recipe for confusion anyways.

You wrote the code, you should know the hierarchy. Just use MySuperclass.__foobar__(self, etc). Works all the time.

Upvotes: -3

Cory Madden
Cory Madden

Reputation: 5203

object is only a special case in that there is nothing to initialize, so its constructor doesn't accept any args or kwargs. Since the __init__ method on object doesn't do anything there's no need to call it if you're inheriting only from object.

Upvotes: 1

Related Questions