Does a derived class automatically have all the attributes of the base class?
Asked Answered
O

2

26

There seems to be no good online documentation on this: If I make a derived class, will it automatically have all the attributes of the base class? But what's the BaseClass.__init() for, do you also need to do it to other base class methods? Does BaseClass.__init__() need arguments? If you have arguments for your base class __init__(), are they also used by the derived class, do you need to explicitly set the arguments to the derived classe's __init__(), or set them to BaseClass.__init__() instead?

Overspend answered 18/6, 2011 at 13:45 Comment(0)
A
62

If you implement __init__ in a class derived from BaseClass, then it will overwrite the inherited __init__ method and so BaseClass.__init__ will never be called. If you need to call the __init__ method for BaseClass (as is normally the case), then its up to you to do that, and its done explicitly by calling BaseClass.__init__, normally from within the newly implemented __init__ method.

class Foo(object):
    def __init__(self):
        self.a = 10

    def do_something(self):
        print self.a

class Bar(Foo):
    def __init__(self):
        self.b = 20

bar = Bar()
bar.do_something()

This will cause the following error:

AttributeError: 'Bar' object has no attribute 'a'

So, the do_something method has been inherited as expected, but that method requires the attribute a to have been set, which it never is because __init__ was also overwritten. We get round this by explicitly calling Foo.__init__ from within Bar.__init__.

class Foo(object):
    def __init__(self):
        self.a = 10

    def do_something(self):
        print self.a

class Bar(Foo):
    def __init__(self):
        Foo.__init__(self)
        self.b = 20

bar = Bar()
bar.do_something()

which prints 10 as expected. Foo.__init__ in this case expects a single argument which is an instance of Foo (which by convention is called self).

Normally, when you call a method on an instance of a class, the class instance is passed automatically as the first argument. Methods on an instance of a class are called bound methods. bar.do_something is an example of a bound method (and you'll note that it is called without any arguments). Foo.__init__ is an unbound method because it is not attached to a particular instance of Foo, so the first argument, an instance of Foo, needs to be passed explicitly.

In our case, we pass self to Foo.__init__, which is the instance of Bar that was passed to the __init__ method in Bar. Since Bar inherits from Foo, instances of Bar are also instances of Foo, so passing self to Foo.__init__ is allowed.

It is likely be the case that the class you are inheriting from requires or accepts more arguments than just an instance of the class. These are dealt with as you would with any method you're calling from within __init__:

class Foo(object):
    def __init__(self, a=10):
        self.a = a

    def do_something(self):
        print self.a

class Bar(Foo):
    def __init__(self):
        Foo.__init__(self, 20)

bar = Bar()
bar.do_something()

which would print 20.

If you're trying to implement a interface that fully exposes all the initialisation arguments of the base class through your inheriting class, you'll need to do so explicitly. This is typically done with the *args and **kwargs arguments (the names are by convention), which are placeholders for all rest of the arguments that aren't explicitly named. The following example makes use of everything I've discussed:

class Foo(object):
    def __init__(self, a, b=10):
        self.num = a * b

    def do_something(self):
        print self.num

class Bar(Foo):
    def __init__(self, c=20, *args, **kwargs):
        Foo.__init__(self, *args, **kwargs)
        self.c = c

    def do_something(self):
        Foo.do_something(self)
        print self.c


bar = Bar(40, a=15)
bar.do_something()

In this case, the argument c is set to be 40, as it's the first argument to Bar.__init__. The second argument is then incorporated into the variables args and kwargs (the * and ** is specific syntax that says expand the list/tuple or dictionary into separate arguments when passing to a function/method), and is passed on to Foo.__init__.

This example also makes the point that any overwritten method needs to be called explicitly if that is what is required (as do_something is in this case).

One final point, you will often see super(ChildClass, self).method() (where ChildClass is some arbitrary child class) being used instead of a call to the BaseClass method explicitly. Discussion of super is a whole other question, but suffice it to say, in these cases it's typically being used to do exactly what is being done by calling BaseClass.method(self). Briefly, super delegates the method call to the next class in the method resolution order - the MRO (which in single inheritance is the parent class). See the documentation on super for more info.

Acutance answered 18/6, 2011 at 14:58 Comment(3)
@PM2Ring where do you mean?Acutance
@PM2Ring No, that's the point. Bar extends Foo with the c attribute. Foo can't take a keyword c. Unless I'm missing something?Acutance
Sorry, it looks like I was a little confused (and I couldn't easily test it on my phone).Canute
H
11

If I make a derived class, will it automatically have all the attributes of the base class?

Class attributes, yes. Instance attributes, no (simply because they don't exist when the class is created), unless there's no __init__ in the derived class, in which case the base one will be called instead, and will set the instance attributes.

Does BaseClass.init() need arguments?

Depends on the class and its __init__ signature. If you're explicitly calling Base.__init__ in the derived class, you will at least need to pass self as the first argument. If you have

class Base(object):
    def __init__(self):
        # something

then it's rather obvious that no other arguments are accepted by the __init__. If you'd have

class Base(object):
    def __init__(self, argument):
        # something

then you have to pass argument when calling base __init__. No rocket science in here.

If you have arguments for your base class init(), are they also used by the derived class, do you need to explicitly set the arguments to the derived classe's init(), or set them to BaseClass.init() instead?

Again, if the derived class doesn't have __init__, base one will be used instead.

class Base(object):
    def __init__(self, foo):
        print 'Base'

class Derived(Base):
    pass

Derived()   # TypeError
Derived(42) # prints Base

In other case, you need to take care of it somehow. Whether you use *args, **kwargs and just pass arguments unmodified to the base class, or copy the base class signature, or supply arguments from elsewhere, depends on what you're trying to accomplish.

Hyoscyamine answered 18/6, 2011 at 14:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.