# 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/>&#x20;

## 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:

```python
# 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**.&#x20;

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.

```python
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:

```python
apple1 = Apple('Apple 1', 21)
```

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

{% hint style="info" %}
You can either pronounce \_\_init\_\_ as "underscore underscore init underscore underscore" or just call it "dunder init".
{% endhint %}

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.

{% hint style="info" %}
List is an object, just like everything else you've seen so far in Python, except functions and variables.
{% endhint %}

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\_\_.&#x20;

```python
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.

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

```python
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.

```python
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:

```python
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](https://memoryerror.gitbook.io/python-basics/master))

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