Object Oriented Programming in Python (Beginners)

In addition to providing outstanding support for simple scripts using it's variety of built-in objects, Python also has excellent support for object oriented programming as well.

Bear with me here, some of these definitions can be a bit obtuse the first time you encounter them -- but they'll become clear as we work through examples. In Python and other languages, an object is an instance of a class, which defines a type. Generally speaking, object oriented programming languages are languages that support three essential features:

  • Encapsulation - Bundling of data and methods (functions that can operate on the data) together in objects.
  • Inheritance - A mechanism allowing objects to be based on other object definitions and add specific behavior.
  • Runtime polymorphism - The ability of objects that inherit other objects to override or change the behavior of methods defined in their parents.

Classes define types

In Python as in most other languages (JavaScript is an exception), objects are instances of a class. JavaScript users tend to call such languages "classical", so at the risk of a play on words, let's look at a classical example.

In [1]:
class Animal:                                                                                          
    def __init__(self, message="Generic animals can't talk!  We don't know what to say!"):
        self.message = message                                                                     
        print("Creating an instance of: " + type(self).__name__)                                   
        
    def speak(self):                                                                               
        print(self.message)                                                                        
        
    def move(self):  
        print("Moving")

The code above looks a little complex, so let's break it down.

First we declare the class (line 1). Next, on line 2-4, an __init__ method is optional. If provided, it is called automatically to initialize the class instance, after a new instance is created. (If you've programmed in other languages, you may be tempted to call this a constructor, but that's technically incorrect because by the time it's called, the instance already exists). In Python, the first argument to __init__ (line 2) and other object methods is a reference to the object itself. The name of this parameter can (technically) be any valid parameter name, but by convention it is almost always called "self".

The __init__ method is very often used to pass in any data or other objects that the object needs to work or interact with once it is initialized. Such parameters must come after the "self" parameter. We illustrate that here on line 2, passing in message with a default value. On line 3, we use the class instance, self, to store the value of the passed in message in an instance variable, which we can use elsewhere in the object and in our program. As is true for all variables in Python, we didn't have to declare it before using it, the variable is created when we first assign a value to it.

We also log (line 3) what type class of Animal we're creating an instance of. Won't that always be "Animal"? Well, as we'll see when we look at inheritance, the init function will also be called for subclasses (classes that inherit from Animal).

Later on, lines 6-7, we create a new "member function", speak, which simply ouptuts the instance data we set in init. This is an example of encapsulation, the coupling of the data (message) with the code that operatest on the data (speak).

Finally, we create another method on lines 9-10.

If you run the code above, you won't see any output, because no animals (objects of type Animal) exist yet. All we have is a sort of cookie cutter that we can push into the dough to cut out an animal. Let's do that now.

In [2]:
dog = Animal("Woof")
dog.speak()
dog.move()
Creating an instance of: Animal
Woof
Moving

Inheritance and Polymorphism

Now let's use the inheritance mechanism to create a specialized class of Animal, a Dog. We pass the parent class after the class name in parentheses to create a new "child class" or "subclass" of the parent class.

We'll leave speak alone since we can always pass in woof, but let's override the "move" method to get a more dog-like behavior. Also, we're not limited to changing existing methods, we can also create a new method wag, since what good is a dog if he's not really excited to see you when you get home. (People who don't understand what I just said are called "cat people" -- but creating a "cat" class is left as an excercise).

The result is below.

In [3]:
class Dog(Animal):
        
    # Override move to move like a dog
    def move(self):
        print("Running like a happy dog.")
        
    # Create a new method
    def wag(self):
        print("Wagging my tail!")

dog = Dog()
dog.speak()
dog.move()
dog.wag()
Creating an instance of: Dog
Generic animals can't talk!  We don't know what to say!
Running like a happy dog.
Wagging my tail!