A GENTLE  INTRODUCTION TO PYTHON CLASSES FOR NEWBIES
Tags: python, oop
Published: 2020-01-10 10:55:42

Python is an object-oriented programming language (OOP), it is a very popular programming paradigm and is one of the most effective approaches to writing software.

Almost everything in Python is an object, with its properties and methods. So when solving real-world challenges, you create objects based on classes which are also known as instantiation(instances).

Class is defined as an object's blueprint or description and it is created with the keyword class.

Let me illustrate what a class looks like.

Create a class named Cake, so what do you know about cakes, you know we use flour to make the dough and an oven to heat the dough.
So we create an attribute named flour and oven.
When we want to bake a cake, we preheat the oven and mix our flour with other ingredients, so we create a method called preheat_oven and mix_dough.


```python
class Cake:
    def __init__(self, flour, oven):        
        """Initialize attributes."""
        self.flour = flour
        self.oven = oven

    def preheat_oven(self):
        """create a method"""
        return f"Pre-heat {self.oven} to 350 degrees"
    def mix_dough(self):
        return f" Mix together {self.flour}, cocoa powder, baking powder" 

``` 

Now that our Cake class is created, we would create an object(instance) of the class called pound_cake.

```python
pound_cake = Cake()
```

There’s a lot I have done here, but don’t worry I will break it down with the post.


### 1. The __init__() Method

All classes have a function called __init__(), which is always executed when we create a new instance based on the class and it is used to assign values to object properties, as the example above, we used __init__ () function to assign values for oven and flour.
Whenever we want to make an instance from the Pizza class, we’ll provide values for only the last two parameters flour and oven.

### 2. The self Parameter
The two variables defined (oven and flour) each have the prefix self.
i. Any variable prefixed with self indicates the variable is available to every method in the class.
ii. It is used to access variables that belong to the class.

### 3. Methods
Classes can have methods defined in order to add functionalities.
Methods are functions that belong to the object.
Remember all methods have self as their first parameter

```python
def preheat_oven(self):
    pass
```

###4.  Accessing attributes and calling methods.
To access the attributes of an instance, you use dot notation.
We created an instance of the Pizza class called pound_cake, so in order to access attribute flour, we do the following.
```python
pound_cake = Cake("wheat flour","oven")
print(pound_cake.flour)
```
```bash
wheat flour
```

After we created an instance from the class Cake, we can use dot notation to call any method defined in Cake.

```python
print(pound_cake.preheat_oven())
print (pound_cake.mix_dough())

#We can also create multiple instances too
sponge_cake =Cake('pastry flour', 'Roaster oven')
chiffon_cake = Cake ('whole wheat flour', 'Roaster oven')
print(chiffon_cake.mix_dough())

```

```bash
Pre-heat oven to 350 degrees
 Mix together wheat flour, cocoa powder, baking powder
 Mix together whole wheat flour, cocoa powder, baking powder
```

### 5. Modifying an Attribute’s Value through a Method
It can be helpful to have methods that update certain attributes for you. Instead of accessing the attribute directly, you pass the new value to a method that handles the updating internally.

Write a class that allows users to choose their favorite pizza toppings and favorite crust type from a pizza app. 
Now the store is running a promo when you order a Margherita pizza, you can order a free drink.

Modify the drink attribute through the order_margherita method

```python
class Pizza:
    def __init__(self, veggies, toppings, crust, oil):
        self.veggies = veggies
        self.toppings = toppings
        self.crust = crust
        self.oil = oil
 
    def order_margherita(self, drink):
        """modify attribute value here"""
        self.drink = drink
        return f"You ordered for {self.crust} pizza with the {self.veggies},{self.toppings},{self.oil} toppings and you have gotten a free {drink}"



margherita_pizza = Pizza( "tomatoes", "mozzarella" ,"thick" ,"extra virgin olive oil.")
print(margherita_pizza.order_margherita("coke"))

```

```bash 
You ordered for thick pizza with the tomatoes, mozzarella, extra virgin olive oil. toppings and you have gotten a free coke.
```
### 6. Assigning a Default Value for an Attribute
When you assign a default attribute value, you don’t have to include a parameter for that attribute.

Still using our Pizza class, let me assign a default value for meat attribute and diary.

```python
class Pizza:
    def __init__(self, veggies, toppings, crust, oil):
        self.veggies = veggies
        self.toppings = toppings
        self.crust = crust
        self.oil = oil
        self.meat = "beef"
        self.dairy = "cheese"

    def order_margherita(self, drink):
        """modify attribute value here"""
        self.drink = drink
        return f"You ordered for {self.crust} pizza with the {self.veggies},{self.toppings},{self.oil} toppings and you have gotten a free {drink}"

    def order_pepperoni(self):
        return f"You ordered {self.crust} pizza topped with {self.meat} and {self.dairy}"

order_pepperoni = Pizza( "chilli","oregano," ,"thin" ,"extra virgin olive oil.")
print(order_pepperoni.order_pepperoni())
```

```bash
You ordered thin pizza topped with beef and cheese
```

    

# INHERITANCE
Inheritance allows us to define a class that inherits all the methods and properties of another class.

The parent class is the class being inherited from, also called a base class.
The child class is the class that inherits from another class.

The syntax looks like this

```python
class ChildClass(ParentClass):
    pass
```

Create a child class named OrderPizza, which will inherit the properties and methods from the Pizza class
```python
class OrderPizza(Pizza):
   pass

order_pizza = OrderPizza( "chilli","oregano","thin" ,"soya bean oil.") #create an instance
print(order_pizza.order_pepperoni())   #call a method

```
```bash
You ordered thin pizza topped with beef and cheese
```

#### Adding __init__() method and super() to a Child Class.
i. When you add the __init__() function, the child class will no longer inherit the parent's __init__() function,in order
to keep the inheritance of the parent's __init__() function, add a call to the parent's __init__() function:
```python
class OrderPizza(Pizza):
    def __init__(self, veggies, toppings, crust, oil):
        Pizza.__init__(self, veggies, toppings, crust, oil)
        
pizza = OrderPizza( "tomatoes", "oregano" ,"thin","extra virgin olive oil.")
print(pizza.order_margherita("coca-cola"))

```
```bash
You ordered for thin pizza with the tomatoes, oregano, extra virgin olive oil. toppings and you have gotten a free coca-cola
```
ii. Python also has a super() function that will make the child class inherit all the methods and properties from its parent.

```python
class OrderPizza(Pizza):
    def __init__(self, veggies, toppings, crust, oil):
        super().__init__(veggies, toppings, crust, oil)
pizza = OrderPizza( "tomatoes", "oregano" ,"thin","extra virgin olive oil.")
print(pizza.order_margherita("coca-cola"))
```
```bash
You ordered for thin pizza with the tomatoes,oregano,extra virgin olive oil. toppings and you have gotten a free coca-cola
```
#### Adding Attributes and Methods for the Child Class
Let’s add an attribute that allows the user to choose the delivery type and a method to order their pizza delivered.
```python
class OrderPizza(Pizza):
    def __init__(self, veggies, toppings, crust, oil):
        Pizza.__init__(self, veggies, toppings, crust, oil)
        self.delivery="Bike man"
    def ride_confirmation(self):
        return f"A {self.delivery} will deliver your pizza in twenty minutes"
        
pizza = OrderPizza( "tomatoes", "oregano" ,"thin","soya oil")
print(pizza.ride_confirmation())
```
```bash
A Bike man will deliver your pizza in twenty minutes
```


### CLASS AND STATIC METHODS

#### Class Methods
What we have looked at so far are self.attributes and methods.
The @classmethod decorator is a function decorator that instantiates an instance of a class using a different parameter(s) than those usually passed to the class constructor.
i. They are usually marked with a classmethod decorator and takes an implicit first argument ( cls )
ii. It doesn't return the instance of a class but a new object of the class cls.

An example below, maybe the Pizza startup wants to serve clients that are watching their sodium intake.
```python
class OrderPizza(Pizza):
    def __init__(self, veggies, toppings, crust, oil):
        Pizza.__init__(self, veggies, toppings, crust, oil)
        self.delivery="Bike man"
    def ride_confirmation(self):
        return f"A {self.delivery} will deliver your pizza in twenty minutes"
    
    @classmethod
    def healthier_food(cls, vegetable):
        return f"Make me a {vegetable} salad"

order_salad = OrderPizza.healthier_food("parsley")
print(order_salad)
```
```bash
Make me a parsley salad
```


#### Static Methods
Static methods are similar to class methods except that they don't receive an implicit first argument.

i. They are usually marked with staticmethod decorator
ii. They behave like plain functions except that you can call them from the instance of the class.

An example below, maybe the Pizza store is out of bacon, it alerts the user to select another topping.
```python
class OrderPizza(Pizza):
    def __init__(self, veggies, toppings, crust, oil):
        Pizza.__init__(self, veggies, toppings, crust, oil)
        self.delivery="Bike man"
    def ride_confirmation(self):
        return f"A {self.delivery} will deliver your pizza in twenty minutes"
    
    @classmethod
    def healthier_food(cls, vegetable):
        return f"Make me a {vegetable} salad"
    
    @staticmethod
    def order_bacon_pizza(topping):
        if topping == "bacon":
            return f"we don't have bacon in the store now, please choose another"
        else:
            return f"your pizza with {topping} is ready"
            
toppings = ["cabbage", "bacon","beef"]
for topping in toppings:
    pizza = OrderPizza.order_bacon_pizza(topping)
    print(pizza)
```

```bash
your pizza with cabbage is ready
we don't have bacon in the store now, please choose another
your pizza with beef is ready
```


YAY! 🙌 🎉🎉 We are almost done, let us test our Pizza class (you can test for the child class later).
The key to developing quality software is to test your code as you write it.

Create a new file (test.py) and add the following code below.
```python
import unittest
from pizza import Pizza


class TestPizza(unittest.TestCase):
    """Tests for the Pizza class"""
    def test_make_marinara(self):
        pizza = Pizza("tomatoes", "oregano", "thin", "coconut oil.")
        pizza = pizza.make_marinara()
        self.assertIn('oregano', pizza)
        self.assertEqual(
            'You ordered for thin pizza with the following toppings tomatoes,oregano,coconut oil.',
            pizza)
        self.assertNotEqual("order a pizza", pizza)
        self.assertNotIn("cheese", pizza)

    def test_order_pepperoni(self):
        pizza = Pizza("tomatoes", "oregano", "thin", "coconut oil.")
        pizza = pizza.order_pepperoni()
        self.assertEqual('You ordered thin pizza topped with beef and cheese',
                         pizza)
        self.assertNotEqual("order a pepperoni pizza", pizza)


unittest.main()
```
```bash

..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK
```

I really hope you liked the article, please feel free to share your opinions about it, hopefully, I hope to recreate this in JavaScript.

#### References

1. Python Crash Course by Eric Mathes
2. Solo learn
3. W3school


Let us connect [Twitter](https://twitter.com/oluchiorji_95) | [LinkedIn]()