Object Oriented Programming

Hey guys, welcome back to the Python basics tutorial! I did say we're going to start some fun stuff from now on. So today you'll learn about a almost totally different style of programming: Object Oriented Programming (OOP)

Programming Paradigms

I like to think of paradigms as "styles". Python has four paradigms: Functional, Procedural, Imperative and Object Oriented. Since they're not specific to Python, they're out of this tutorial's scope, but if you're interested: https://blog.newrelic.com/engineering/python-programming-styles/

Alright, alright, what's this all about?

NOTE: I really couldn't explain classes and objects properly, so feel free to ask Google.

So far, you've been programming with functions and variables. Let's say you wanted to represent an apple with code. You want to let the user take a bite, or throw it away. Let's try writing some code for that with dictionaries and functions with proper error handling:

# NOTE: This code is for representing a SINGLE apple only but written as
# if we are using multiple.

apple_data = {'Apple 1': {'bites': 20}}]
def bite(apple_name):
    try:
        apple = apple_data[apple_name]
    except KeyError:
        print('Wrong apple name')
        return
    apple['bites'] -= 1
    print(str(apple['bites']), 'bites left!') # Convert bites to a string

def throw(apple_name):
    try:
        apple_data.pop(apple_name)
        print('Threw apple away')
    except KeyError:
        print('Wrong apple name')
        return

As we added more apples, the dictionary would grow larger and larger and larger. There's a lot of repetition too. Would you like a simple way to represent a real object, such as an apple?

Enter OOP.

Classes and objects

A class is the blueprint of an object. Like a template. You fill out values and make an object.

This may be slightly confusing, so let's take another practical example. We have a Human class. We define that a Human must have a name and an age. So it's a template. We can create a Human object from it by filling in the values Name and Age.

You define a class with the class keyword.

class Apple:
    def __init__(self, name, bites):
        self.name = name
        self.bites = bites

So, here we define a class called apple. The rest is a bit confusing, you'll understand later on.

When creating a class, you're creating the skeleton of an object. Now, to make real objects you pass in values like this:

apple1 = Apple('Apple 1', 21)

OK, we got the basic part. Now we'll move on to attributes and methods.

You can either pronounce __init__ as "underscore underscore init underscore underscore" or just call it "dunder init".

We'll see more about __init__ and self right away, but before that we need to get a few things out of the way first.

Attributes and methods

Do you remember the list.index(), list.pop() functions? Well, they're not really regular functions, they're methods. Methods are functions which are bound to an object.

List is an object, just like everything else you've seen so far in Python, except functions and variables.

By "bound" to an object, if you try to call index outside of a list, you'll get a NameError, because it doesn't exist! Attributes are the same, but they're variables bound to an object, not functions. Methods and attributes are accessed by: object.attribute or object.method().

__init__ and self

__init__ is the constructor, it initializes the object. As we said, we have values to fill in for the template. We take those values via __init__. To take arguments for name and bites which we have to set as attributes, we specify them in __init__.

def __init__(self, name, bites):
    self.name = name
    self.bites = bites

So, think of calling __init__ when initializing an object.

You probably noticed we also used a self parameter in __init__ definition, self is a special variable that points to the object. When we bind name and bites to self, we're saying that this object will have the attributes name and bites.

self is just a way of referring to the object from within a class. Which means, self.bites is actually apple1.bites outside of the class. And, having self as the first parameter of a function makes it a method of that object.

Now we can access it outside of the object too. So, let's try it:

class Apple:
    def __init__(self, name, bites):
        self.name = name
        self.bites = bites

apple1 = Apple('Apple 1', 30)
print(apple1.name) # Prints 'Apple 1'
print(apple1.bites) # Prints 30

Now it clicks!

Adding more methods

OK, let's try to implement a bite method.

class Apple:
    def __init__(self, name, bites):
        self.name = name
        self.bites = bites
    
    def bite(self): # Since we do not need any data for bite, we just use self.
        self.bites -=1

That's it. Two more lines.

Implementing the Apple

OK, so now we hopefully got the basics. Let's try to implement the example we did with functions at the beginning of the chapter, but this time with OOP:

class Apple:
    def __init__(self, name, bites):
        self.name = name
        self.bites = bites
        self.thrown = False
    
    def bite(self):
        if not self.thrown:
            self.bites -= 1
        else:
            raise Exception('Apple has been thrown away.')
    
    def throw(self):
        self.thrown = True

That's it! Doesn't it look much cleaner and readable? If you're still confused, feel free to contact me. (link in Introduction)

OOP is a complex and diverse subject, you'll learn more such as Inheritance, Polymorphism, Encapsulation etc. when you're past the beginner stage.

Last updated