Inheritance is an OOP idea that specializes a class based on a general class. The specialized class inherits the attributes from the general class but has some special-case behavior. The general class is called the superclass (or parent class or base class), while the specialized class is called the subclass (or child class).
The concept of inheritance stems from the desire to reduce redundancy. Any class that has the same functionality and attributes as a more general class should inherit those traits, as opposed to creating them again. The Boeing_747 class should, for instance, inherit from the Plane class, because the Boeing_747 class shares many functions with Plane.
class Plane: class Boeing_747: class Boeing_747(Plane): def __init__(self, num_passengers): def __init__(self, num_first_class, num_economy): def __init__(self, num_first_class, num_economy): ... ... ... def fly(self) def fly(self): # Boeing_747(Plane) indicates that Boeing_747 inherits from Plane ... ... def refuel(self) def refuel(self): ... ...
In the first Boeing_747 class,
Boeing_747 exist as separate classes, each with their own functions. Because they share
Boeing_747 can inherit from
Plane and not have to list out the overlapping functions. In the second example,
Boeing_747 is a subclass of
Inheritance allows users to create branching, related systems of classes instead of individual static classes. Because inherited functions and variables are controlled from the super class, the user simply needs to change the inherited values and they will be reflected in all subclasses.
Inheritance vs. composition
Inheritance represents an "is-a" relationship between classes. For example, a
Rectangle is a
Shape, and a
Square is a
class Shape: def __init__(self, type): self.type = type class Rectangle(Shape): def __init__(self, length, width): super().__init__("rectangle") self.length = length self.width = width class Square(Rectangle): def __init__(self, side): super().__init__(side, side) Shape.__init__(self, "square")
Shape is the superclass of
Rectangle is the superclass of
Shape has a type, which is set to "rectangle" and "square" for
Square, respectively. A
Rectangle has its own attributes, length and width. Square inherits all its attributes from
Rectangle and sets its own type.
Composition represents a "has-a" relationship between classes and objects. For example, a
Line has end
class Point: def __init__(self, x, y): self.x = x self.y = y class Line: def __init__(self, point1, point2): self.start = point1 self.end = point2
Function and constructor lookup
A subclass that doesn't include a certain function, can lookup the function in its super class.
class Plane: class Boeing_747(Plane): def __init__(self, num_Passengers): def __init__(self, num_First_Class num_Economy): self.p = num_Passengers self.f = num_First_Class self.fuel = 0 self.e = num_Economy self.fuel = 0 # (Plane) after Boeing_747 indicates that Boeing_747 def fly(self): # inherits from Plane print("Takeoff!") def refuel(self): self.fuel += 100
>>> wikiPlane = Boeing_747(25, 200) >>> wikiPlane.fly() Takeoff! >>> wikiPlane.refuel() >>> wikiPlane.fuel 100
Here a Boeing_747 object is created, and the user has the Boeing_747 object call
refuel. Although the class Boeing_747 does not define
refuel, it looks into its super class
Plane and calls the functions defined there.
Function and attribute overriding
In the case that a subclass wants to alter a function in its superclass, a subclass can override a superclass's function. The subclass can override a superclass function by defining a function with the exact same name and input arguments as the superclass function. It can then define the function differently from its superclass implementation. The subclass can build on the superclass function as well. A subclass can call a superclass function by invoking the superclass name on the superclass function.
class Plane: class Boeing_747(Plane): class Airforce_One(Boeing_747): def __init__(self, num_Passengers): def __init__(self, num_First_Class num_Economy): def __init__(self, president): self.p = num_Passengers self.f = num_First_Class self.VIP = president self.fuel = 0 self.e = num_Economy self.fuel = 100 self.fuel = 0 def fly(self): def fly(self): def fly(self): print("Takeoff!") print("Boeing 747 clear for...") print("Renegade is secure") Plane.fly(self) Boeing_747.fly(self) def refuel(self): self.fuel += 100 #Boeing_747's version of fly() prints the string "Boeing 747 clear for...", #Airforce_Ones's version of fly() prints the string "Renegade is secure", #then calls Plane's fly() function #then calls Boeing_747's fly() function >>> wikiPlane = Boeing_747(25, 200) >>> wikiPlane.fly() Boeing 747 clear for... Takeoff! >>> >>>ObamasRide = Airforce_One("Obama") >>>ObamasRide.fly() Renegade is secure Boeing 747 clear for... Takeoff!
A class may inherit from multiple superclasses. The method resolution order (MRO) is how Python decides where to look for an attribute of a class that inherits from several classes. For example, we define the following classes:
class A: def me(self): print("Called A.me") class B: def me(self): print("Called B.me") class C(A, B): pass
C inherits from both
B. We call
C().me() — since
C does not define a method
me, Python checks the MRO:
>>> C.__mro__ (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
Python first looks in
C doesn't define a method
me, Python continues in the MRO and sees that
A defines a method
me. Python ends up calling
>>> C().me() Called A.me