Does a subclass require a constructor/initializer? - python

Usually when I see a subclass, it calls the superclass in the constructor like below:
class Boss(object):
def __init__(self, name, attitude, behaviour, face):
self.name = name
self.attitude = attitude
self.behaviour = behaviour
self.face = face
def get_attitude(self):
return self.attitude
def get_behaviour(self):
return self.behaviour
def get_face(self):
return self.face
class GoodBoss(Boss):
def __init__(self, name, attitude, behaviour, face):
super().__init__(name, attitude, behaviour, face)
However on a website I saw an example of a subclass:
class Parent(object):
def __init__(self):
self.value = 5
def get_value(self):
return self.value
class Child(Parent):
pass
In the last example why doesn't the Child subclass include super().__init__()?

Try to understand this simple code:
class Parent(object):
def __init__(self):
print('Parent is called!')
class ChildA(Parent):
pass
class ChildB(Parent):
def __init__(self):
print('ChildB is called!')
class ChildC(Parent):
def __init__(self):
print('ChildC is called!')
super().__init__()
p = Parent()
print()
ca = ChildA()
print()
cb = ChildB()
print()
cc = ChildC()
print()
print(ChildA.mro())
print(ChildB.mro())
print(ChildC.mro())
Output:
Parent is called!
Parent is called!
ChildB is called!
ChildC is called!
Parent is called!
[<class '__main__.ChildA'>, <class '__main__.Parent'>, <class 'object'>]
[<class '__main__.ChildB'>, <class '__main__.Parent'>, <class 'object'>]
[<class '__main__.ChildC'>, <class '__main__.Parent'>, <class 'object'>]
Key Observations:
If a class does not have an implementation of the __init__() method, then its parent's __init__() will be called if it exists.
If a class has an implementation of the __init__() method, then it will be called. However its parent's __init__() will not be called automatically.
If a class has an implementation of the __init__() method, then it will be called. We can call its parent's __init__() method by explicitly calling it with super().method(*args, **kwargs)

Classes don't strictly require __init__() methods at all. You can have a class consisted with only class/static attributes/methods and it'll be just as valid, though its usefulness remains to be debated.
The reason you see most classes have __init__() method is because most of the time when a class is instantiated you'd want to initialize the class with some values/handling. If your Child class is not doing anything different than the Parent's __init__() method, there's no need to redefine __init__().
If the Child class needs to redefine __init__ but still wants to rely on the Parent's __init__ method, then that's when you need super().__init__().

In your first example, Subclass is calling the init method of your parent class in the constructor method. This approach is basically used when we need to set some attributes of subclass method but after/before calling the superclass method. Let's consider below example with setting the age attribute in subclass:
class Boss(object):
def __init__(self, name, attitude, behaviour, face):
self.name = name
self.attitude = attitude
self.behaviour = behaviour
self.face = face
def get_attitude(self):
return self.attitude
def get_behaviour(self):
return self.behaviour
def get_face(self):
return self.face
class GoodBoss(Boss):
def __init__(self,
name,
attitude,
behaviour,
face, age):
super().__init__(name, attitude, behaviour, face)
self.age = age # here age is an attribute of only GoodBoss class, Not Superclass
Here your subclass is setting the age attribute which isn't an attribute of your parent class.This pattern is used when your subclass is doing something different from superclass in constructor function.
In second example, we are not setting/calling any method in constructor function of child class. When we make the object of child class, the parent class init function would be called.Your subclass is not doing any different.
These are completely depend on the code design.

Related

I want to call parent class method which is overridden in child class through child class object in Python

class abc():
def xyz(self):
print("Class abc")
class foo(abc):
def xyz(self):
print("class foo")
x = foo()
I want to call xyz() of the parent class, something like;
x.super().xyz()
With single inheritance like this it's easiest in my opinion to call the method through the class, and pass self explicitly:
abc.xyz(x)
Using super to be more generic this would become (though I cannot think of a good use case):
super(type(x), x).xyz()
Which returns a super object that can be thought of as the parent class but with the child as self.
If you want something exactly like your syntax, just provide a super method for your class (your abc class, so everyone inheriting will have it):
def super(self):
return super(type(self), self)
and now x.super().xyz() will work. It will break though if you make a class inheriting from foo, since you will only be able to go one level up (i.e. back to foo).
There is no "through the object" way I know of to access hidden methods.
Just for kicks, here is a more robust version allowing chaining super calls using a dedicated class keeping tracks of super calls:
class Super:
def __init__(self, obj, counter=0):
self.obj = obj
self.counter = counter
def super(self):
return Super(self.obj, self.counter+1)
def __getattr__(self, att):
return getattr(super(type(self.obj).mro()[self.counter], self.obj), att)
class abc():
def xyz(self):
print("Class abc", type(self))
def super(self):
return Super(self)
class foo(abc):
def xyz(self):
print("class foo")
class buzz(foo):
def xyz(self):
print("class buzz")
buzz().super().xyz()
buzz().super().super().xyz()
results in
class foo
Class abc

Call parent method inside super().__init__ when method is overriden by child

I have overriden a method in a child class.
This method is used in the parent class constructor.
When calling super().__init__ in the child class the child's method is executed instead of the parent's one inside the parent's constructor.
Example:
class A:
def __init__(self):
self.method()
def method(self):
print('parent method!')
class B(A):
def __init__(self):
super().__init__()
def method(self):
print('child method!')
b = B()
Output: child method!
While I want to get parent method!
Edit:
I need the parent's class constructor to use the not-overriden method, but after that each call to method() from the parent class should call overriden one.
Because I need the parent's contructor to be called with the no-overriden method but then I need to use the overriden one each time.
This indicates you are asking one method to do too many things. Split method into two parts: one that is not overridden and called by A.__init__, and another that can be overriden. Something like
class A:
def __init__(self):
self._init_method()
def _init_method(self):
print('parent method!')
def method(self):
self._init_method()
class B(A):
def __init__(self):
super().__init__()
def method(self):
print('child method!')
b = B()
_init_method does what you claim needs to be done from A.__init__. At the same time, you can have method do the same thing, unless you override it, in which case you'll do whatever it is you want B.method to do.
If you have that kind of situation, I would suggest something like:
class B(A):
def __init__(self):
super().__init()
self.n_method = 1
def method(self):
if self.n_method == 1:
super().method()
else:
do method B stuffs
self.n_method += 1
In A's __init__() method, you could write A.method(self) rather than self.method() to be more explicit about which method to call.

Python base class' implicit super() call

Currently I am starting to revise my python's OOP knowledge. I stumbled upon super() definition, which suggests, that it provides a derived class with a set of instance variables and methods from a base class.
So I have this piece of code:
class foo:
bar = 5
def __init__(self, a):
self.x = a
def spam(self):
print(self.x)
class baz(foo):
pass
b = baz(5)
b.spam()
And this executed with no super() calls, no errors, and printed out 5.
Now when I add an __init__ method to the derived class, like this:
class foo:
bar = 5
def __init__(self, a):
self.x = a
def spam(self):
print(self.x)
class baz(foo):
def __init__(self, a):
self.b = a
b = baz(5)
b.spam()
the script gives me an error: AttributeError: 'baz' object has no attribute 'x'.
So this would suggest, that if my class has a default __init__, it also has an explicit super() call. I couldn't actually find any info confirming this, so I just wanted to ask if I am correct.
The problem is that when you define the method __init__ in your subclass baz, you are no longer using the one in the parent class foo. Then, when you call b.spam(), x does not exist because that is define in the __init__ method of the parent class.
You can use the following to fix this if what you want is to call the __init__ method of the parent class and also add your own logic:
class baz(foo):
def __init__(self, a):
super().__init__(10) # you can pass any value you want to assign to x
self.b = a
>>> b = baz(5)
>>> b.spam()
10

Python Class Name as Class Variable

I'm working as an application with classes and subclasses. For each class, both super and sub, there is a class variable called label. I would like the label variable for the super class to default to the class name. For example:
class Super():
label = 'Super'
class Sub(Super):
label = 'Sub'
Rather than manually type out the variable for each class, is it possible to derive the variable from the class name in the super class and have it automatically populated for the subclasses?
class Super():
label = # Code to get class name
class Sub(Super)
pass
# When inherited Sub.label == 'Sub'.
The reason for this is that this will be the default behavior. I'm also hoping that if I can get the default behavior, I can override it later by specifying an alternate label.
class SecondSub(Super):
label = 'Pie' # Override the default of SecondSub.label == 'SecondSub'
I've tried using __name__, but that's not working and just gives me '__main__'.
I would like to use the class variable label in #classmethod methods. So I would like to be able to reference the value without having to actually create a Super() or Sub() object, like below:
class Super():
label = # Magic
#classmethod
def do_something_with_label(cls):
print(cls.label)
you can return self.__class__.__name__ in label as a property
class Super:
#property
def label(self):
return self.__class__.__name__
class Sub(Super):
pass
print Sub().label
alternatively you could set it in the __init__ method
def __init__(self):
self.label = self.__class__.__name__
this will obviously only work on instantiated classes
to access the class name inside of a class method you would need to just call __name__ on the cls
class XYZ:
#classmethod
def my_label(cls):
return cls.__name__
print XYZ.my_label()
this solution might work too (snagged from https://stackoverflow.com/a/13624858/541038)
class classproperty(object):
def __init__(self, fget):
self.fget = fget
def __get__(self, owner_self, owner_cls):
return self.fget(owner_cls)
class Super(object):
#classproperty
def label(cls):
return cls.__name__
class Sub(Super):
pass
print Sub.label #works on class
print Sub().label #also works on an instance
class Sub2(Sub):
#classmethod
def some_classmethod(cls):
print cls.label
Sub2.some_classmethod()
You can use a descriptor:
class ClassNameDescriptor(object):
def __get__(self, obj, type_):
return type_.__name__
class Super(object):
label = ClassNameDescriptor()
class Sub(Super):
pass
class SecondSub(Super):
label = 'Foo'
Demo:
>>> Super.label
'Super'
>>> Sub.label
'Sub'
>>> SecondSub.label
'Foo'
>>> Sub().label
'Sub'
>>> SecondSub().label
'Foo'
If class ThirdSub(SecondSub) should have ThirdSub.label == 'ThirdSub' instead of ThirdSub.label == 'Foo', you can do that with a bit more work. Assigning label at the class level will be inherited, unless you use a metaclass (which is a lot more hassle than it's worth for this), but we can have the label descriptor look for a _label attribute instead:
class ClassNameDescriptor(object):
def __get__(self, obj, type_):
try:
return type_.__dict__['_label']
except KeyError:
return type_.__name__
Demo:
>>> class SecondSub(Super):
... _label = 'Foo'
...
>>> class ThirdSub(SecondSub):
... pass
...
>>> SecondSub.label
'Foo'
>>> ThirdSub.label
'ThirdSub'
A metaclass might be useful here.
class Labeller(type):
def __new__(meta, name, bases, dct):
dct.setdefault('label', name)
return super(Labeller, meta).__new__(meta, name, bases, dct)
# Python 2
# class Super(object):
# __metaclass__ = Labeller
class Super(metaclass=Labeller):
pass
class Sub(Super):
pass
class SecondSub(Super):
label = 'Pie'
class ThirdSub(SecondSub):
pass
Disclaimer: when providing a custom metaclass for your class, you need to make sure it is compatible with whatever metaclass(es) are used by any class in its ancestry. Generally, this means making sure your metaclass inherits from all the other metaclasses, but it can be nontrivial to do so. In practice, metaclasses aren't so commonly used, so it's usually just a matter of subclassing type, but it's something to be aware of.
As of Python 3.6, the cleanest way to achieve this is with __init_subclass__ hook introduced in PEP 487. It is much simpler (and easier to manage with respect to inheritance) than using a metaclass.
class Base:
#classmethod
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
if 'label' not in cls.__dict__: # Check if label has been set in the class itself, i.e. not inherited from any of its superclasses
cls.label = cls.__name__ # If not, default to class's __name__
class Sub1(Base):
pass
class Sub2(Base):
label = 'Custom'
class SubSub(Sub2):
pass
print(Sub1.label) # Sub1
print(Sub2.label) # Custom
print(SubSub.label) # SubSub

Python multi-inheritance, __init__

Regarding multiple parent inheritance, when I call the super.__init__, why doesn't parent2's __init__ function get called? Thanks.
class parent(object):
var1=1
var2=2
def __init__(self,x=1,y=2):
self.var1=x
self.var2=y
class parent2(object):
var4=11
var5=12
def __init__(self,x=3,y=4):
self.var4=x
self.var5=y
def parprint(self):
print self.var4
print self.var5
class child(parent, parent2):
var3=5
def __init__(self,x,y):
super(child, self).__init__(x,y)
childobject = child(9,10)
print childobject.var1
print childobject.var2
print childobject.var3
childobject.parprint()
Output is
9
10
5
11
12
If you want to use super in child to call parent.__init__ and parent2._init__, then both parent __init__s must also call super:
class parent(Base):
def __init__(self,x=1,y=2):
super(parent,self).__init__(x,y)
class parent2(Base):
def __init__(self,x=3,y=4):
super(parent2,self).__init__(x,y)
See "Python super method and calling alternatives" for more details on the sequence of calls to __init__ caused by using super.
class Base(object):
def __init__(self,*args):
pass
class parent(Base):
var1=1
var2=2
def __init__(self,x=1,y=2):
super(parent,self).__init__(x,y)
self.var1=x
self.var2=y
class parent2(Base):
var4=11
var5=12
def __init__(self,x=3,y=4):
super(parent2,self).__init__(x,y)
self.var4=x
self.var5=y
def parprint(self):
print self.var4
print self.var5
class child(parent, parent2):
var3=5
def __init__(self,x,y):
super(child, self).__init__(x,y)
childobject = child(9,10)
print childobject.var1
print childobject.var2
print childobject.var3
childobject.parprint()
You might be wondering, "Why use Base?". If parent and parent2 had inherited directly from object, then
super(parent2,self).__init__(x,y) would call object.__init__(x,y). That raises a TypeError since object.__init__() takes no parameters.
To workaround this issue, you can make a class Base which accepts arguments to __init__ but does not pass them on to object.__init__. With parent and parent2 inheriting from Base, you avoid the TypeError.
Because parent is next in method resolution order (MRO), and it never uses super() to call into parent2.
See this example:
class Base(object):
def __init__(self, c):
print('Base called by {0}'.format(c))
super().__init__()
class ParentA(Base):
def __init__(self, c):
print('ParentA called by {0}'.format(c))
super().__init__('ParentA')
class ParentB(Base):
def __init__(self, c):
print('ParentB called by {0}'.format(c))
super().__init__('ParentB')
class Child(ParentA, ParentB):
def __init__(self, c):
print('Child called by {0}'.format(c))
super().__init__('Child')
Child('Construct')
print(Child.mro())
This will output:
Child called by Construct
ParentA called by Child
ParentB called by ParentA
Base called by ParentB
[<class '__main__.Child'>, <class '__main__.ParentA'>, <class '__main__.ParentB'>, <class '__main__.Base'>, <class 'object'>]
Python multiple inheritance is like a chain, in Child class mro, the super class of ParentA is ParentB, so you need call super().__init__() in ParentA to init ParentB.
If you change super().__init__('ParentA') to Base.__init__(self, 'ParentA'), this will break the inheritance chain, output:
Child called by Construct
ParentA called by Child
Base called by ParentA
[<class '__main__.Child'>, <class '__main__.ParentA'>, <class '__main__.ParentB'>, <class '__main__.Base'>, <class 'object'>]
More info about MRO

Categories

Resources