Inheritance - Object Oriented Programming




Welcome to part 17 of the intermediate Python programming tutorial series. In this part of the series, we're going to discuss inheritance.

Inheritance is a major form of modularity, but actually also plays roles in scaling and maintainability. When creating classes and using Object Oriented Programming, we're usually doing it under the idea that we're making something that either other people will use or our future selves will use. One way to think about it is that you're creating a product that people will use. In my cases, you're creating a tool that people will use. Consider a hammer class. Your hammer will have attributes like a handle, and then a metal head of some kind for pounding nails. This is a minimum viable product (MVP), and should be shipped in this state. You might have various ideas, like maybe having a hammer with two sides to head for various sizes of nails, or maybe some sort of blended head where one side is metal and the other is rubber!

It can be tempting to implement your ideas, because they sound reasonable to you, but you don't want to do that. You will want to let clients modify the hammer class if they want a rubber hammer (the equivalent of buying a rubber hammer instead). Instead, you want to just ship the simplest version of your product, and see what clients think. You may find that almost all of your clients are requesting a feature for the hammer to have one side for pounding nails, and the other side for prying out nails. You hadn't even considered it, but now you're adding something that makes a whole lot more sense in practice.

Inheritance allows you to create super simple classes, and for your users to modify the classes. If you get enough requests for something new, then you can consider adding it, but first let the clients play with it. Back to our Blob example, let's consider inheriting from the Blob class:

class BlueBlob(Blob):
    pass

We're doing nothing in the class, besides inheriting from the Blob class. The Blob class is our "Base" class, or Parent class. It's also our "super" class. I wouldn't necessarily suggest you leave Python and start applying these terms to all other languages, but, in terms of Python, you can call it any of these things, and you will hear it called all of these. This is mostly because these terms are actually coming from people from other languages, who are applying their own OOP terms to Python's.

The new BlueBlob class, inheriting from Blob is our "child" class, or "subclass." When inheriting from another class, you are inheriting everything, all of the methods, including the special methods. Thus, in this case, we're inheriting everything and changing nothing. We can, however, now add our own methods, or even overwrite methods. If, say, we write a new __init__ method, the BlueBlob class will honor ours OVER the original one. We'll see later an issue with this, and how to overcome it. For now, let's change:

def main():
    blue_blobs = dict(enumerate([BlueBlob(BLUE,WIDTH,HEIGHT) for i in range(STARTING_BLUE_BLOBS)]))
    red_blobs = dict(enumerate([BlueBlob(RED,WIDTH,HEIGHT) for i in range(STARTING_RED_BLOBS)]))

We're just simply using the new class. The result should be the same as before:

python tutorials

Blobs doing blob things!...blobjects...if you will.

Now, a common task might be to add our own __init__ method, but what happens if we try this?

class BlueBlob(Blob):
    def __init__(self):
        pass

We get an error:

TypeError: __init__() takes 1 positional argument but 4 were given

We've overwritten the original dunder init method! So do we need to basically write all of the code from the original init method? Nope! We have super() at our disposal!

class BlueBlob(Blob):
    
    def __init__(self, color, x_boundary, y_boundary):
        super().__init__(color, x_boundary, y_boundary)
        self.color = BLUE

Now, we should have all blobs as blue.

The super() call allows us to dynamically refer to the base class. We could also do:

class BlueBlob(Blob):
    
    def __init__(self, color, x_boundary, y_boundary):
        Blob.__init__(self, color, x_boundary, y_boundary)
        self.color = BLUE

The problem here, however, is we'll quickly run into a lot of trouble when doing multiple inheritance. For a while, Python's super() has been somewhat controversial, but there's a great talk by Raymond Hettinger that you should check out called Super considered super!. It's fairly long, but can help you to understand Python's super, and Raymond is a great speaker.

As a side note, you might see him and other people making classes like: SomeClass(object) (note the "object"). This was how one might use the New-Style classes that came about a long time ago. People keep putting it in there, but new-style classes are the default in Python 3, and this is not necessary anymore.

We can also add new methods, like:

class BlueBlob(Blob):
    
    def __init__(self, color, x_boundary, y_boundary):
        Blob.__init__(self, color, x_boundary, y_boundary)
        self.color = BLUE

    def move_fast(self):
        self.x += random.randrange(-5,5)
        self.y += random.randrange(-5,5)

Then within the draw_environment method, we can change blob.move() to blob.move_fast() :

def draw_environment(blob_list):
    game_display.fill(WHITE)

    for blob_dict in blob_list:
        for blob_id in blob_dict:
            blob = blob_dict[blob_id]
            pygame.draw.circle(game_display, blob.color, [blob.x, blob.y], blob.size)
            blob.move_fast()
            blob.check_bounds()
            
    pygame.display.update()

The next tutorial:





  • Intermediate Python Programming introduction
  • String Concatenation and Formatting Intermediate Python Tutorial part 2
  • Argparse for CLI Intermediate Python Tutorial part 3
  • List comprehension and generator expressions Intermediate Python Tutorial part 4
  • More on list comprehension and generators Intermediate Python Tutorial part 5
  • Timeit Module Intermediate Python Tutorial part 6
  • Enumerate Intermediate Python Tutorial part 7
  • Python's Zip function
  • More on Generators with Python
  • Multiprocessing with Python intro
  • Getting Values from Multiprocessing Processes
  • Multiprocessing Spider Example
  • Introduction to Object Oriented Programming
  • Creating Environment for our Object with PyGame
  • Many Blobs - Object Oriented Programming
  • Blob Class and Modularity - Object Oriented Programming
  • Inheritance - Object Oriented Programming
  • Decorators in Python Tutorial
  • Operator Overloading Python Tutorial
  • Detecting Collisions in our Game Python Tutorial
  • Special Methods, OOP, and Iteration Python Tutorial
  • Logging Python Tutorial
  • Headless Error Handling Python Tutorial
  • __str__ and __repr_ in Python 3
  • Args and Kwargs
  • Asyncio Basics - Asynchronous programming with coroutines