Classes & Object-Oriented Programming in Python
Tags: programming, python
Published: 2023-07-24 09:58:25

Object-Oriented programming is a programming paradigm that allows you to create programs based on classes and objects. Look at the following code,

## What is a Python class?
A python class is similar to a function but it uses the class keyword. The syntax for a class looks like this:

```py
class Person:
    '''This is a class named  person'''
    pass
```
When naming classes, always follow PEP 8 style guide, which says that class names should be capitalized like this:
```py
class ClassName:
    pass
```
## What is an Object in Python

Everything in python is an object. Since everything is an object, we can create an object from classes, a process known as instantiating.

```py
house = Person()
```
Note the additional parenthesis at the end. You will get the following output if you print type(house):

```sh
<class '__main__.Person'>
```

## Class Variables
A class will have variables as well. Variables are like attributes and are shared by all instances. For example, suppose the person class has a variable name, as shown below:

```py
class Person:
    '''This is a class named  person'''
    name = 'Joe doe'
```
Every instance of the Person class will have the name i.e Joe doe as shown below:

```py
person1 = Person.name
person2 = Person.name
print(person1)
print(person2)
```
The output will be:

```sh
Joe doe
Joe doe
```

## Class Methods
Classes can also have methods and are defined inside the class. For example, a person can drive a car; let’s define a method in the Person class.

```py
class Person:
    '''This is a class named  person'''
    name = 'Joe doe'

    def can_drive(self):
        print('{} can drive all types of vehicles'.format(self.name))
        
person1 = Person()
print(person1.can_drive())
```


In the code above, we define a method that returns a string and then calls the method on a Person instance. Convention requires that the first argument in class methods be self. Here self refers to the object
The output will be:

```sh
Joe doe can drive all types of vehicles
```

You can also add arguments to the class methods in addition to the self-argument. Let’s add another method with some arguments to our person class.
```py
class Person:
    '''This is a class named  person'''
    name = 'Joe doe'

    def can_drive(self):
        print('{} can drive all types of vehicles'.format(self.name))

    def calculate_bmi(self, weight, height):
        return weight/height
```


In the code above, we add a method calculate_bmi which takes two additional arguments, weight and height, and returns the BMI, which is weight divided by height.

Now when you call the method, you need to pass in values to the method, as shown below:
```py
person1 = Person()
print(person1.calculate_bmi(62,161))
```
The output will be:

```sh
0.38509316770186336
```
## Dunder methods

Dunder methods are methods that are represented by double_underscore on either side, and they include:

- `__init__()`
- `__str__()`
- `__repr__()`

The __init__() method initializes a new object. It is also known as a constructor and is defined as follows:

```py
class Person:
   def __init__(self)
        pass
```
If you defined any arguments in the __init__() method, they would be available every time the class is instantiated.

```py
class Person:
    def __init__(self, name):
        print('{} belongs to the VIP class'.format(name))
```

Let's instantiate a person object:

```py
person_vip = Person('john')
print(person_vip)
```
We will get the following output:
```sh
john belongs to the VIP class
<__main__.Person object at 0x7f87129324f0>
```
`__repr__()` is another dunder method used to give a more meaningful representation of a class. The `__repr__ ()` method usually returns a string. Let’s say you want to get information about the Person class below
```py
class Person:
    def __init__(self, name):
        self.name = name
person = Person('John')
print(person)
```

The output of the code above will be:

```sh
<__main__.Person object at 0x7fb0b38642e0>
```
This doesn’t give any meaningful information about the class. Let’s add a `__repr__()` method to the class Person above.

```py
class Person:
    def __init__(self, name):
        self.name = name
    def __repr__(self):
        return self.name
    

person = Person('John')
print(person)
```


The output will be:

```sh
John
```
## Object-Oriented Programming

The four pillars of object-oriented programming (OOP) are:

- Inheritance
- Polymorphism
- Abstraction
- Encapsulation


### Inheritance

Inheritance is a concept where classes inherit the properties of another class. Suppose we wanted to create a database of all the people in a school. A school can have students, teachers, non-teaching staff, etc. We would then create different classes for the category, as shown below.

```py
class Student:
    pass
class Teacher:
    pass
```


Suppose we wanted to create a method contact  which is shared by all which returns the contact details.

```py
class Student:
    pass
class Teacher:
    pass
```


Rather than writing the same method in both classes, we can create a parent class, define all the methods that the two classes share, and then use inheritance to inherit the methods. here is how the parent class would look like.

```py
class Person: 
  def contact(self): 
    print("print contact details")
```


The student and Teacher classes now automatically inherit the properties of the Person class, as shown below.

```py

class Person: 
  def contact(self): 
    print("print contact details")

class Student(Person):
    pass
 
class Teacher(Person):
    pass
```


If we instantiate a student and teacher object, we will discover that the classes have inherited the contact method.

```py
student1 = Student()
teacher1 = Teacher()
print(student1.contact())
print(teacher1.contact())
```
The output will be:

```sh
print contact details
None
print contact details
None
```
### Polymorphism
Polymorphism is another pillar of Object-Oriented programming where a method is applied to different objects. Suppose you have an Animal class that defines a method called is_branded, as shown below

```py
class Animal():
    def is_branded(self):
       print("This animal is branded")
```

Another class can have the same method i.e, is_branded which returns something different as shown below

```py
class Product():
    def is_branded(self):
       print("This product is branded")

```

The two classes above, i.e., Product and Animal, have no relationship but share a common method which means something different when applied to each class. This concept is called polymorphism

### Abstraction
Abstraction is another pillar of OOP. To abstract means to hide. Abstraction refers to the process in which programs hide the underlying functionality and can be used without the need to know what is happening behind the scenes

For instance, consider the example we use above.

```py
class Person: 
  def contact(self): 
    print("print contact details")

class Student(Person):
    pass
 
class Teacher(Person):
    pass
```
To make the Person class an abstract, it will inherit from Abstract Base Class(ABC), then apply the abstract method to the contact() method as shown below.

```py
from abc import ABC,abstractmethod
class Person(ABC): 

  @abstractmethod
  def contact(self): 
    print("print contact details")

class Student(Person):
    pass
 
class Teacher(Person):
    pass

student1 = Student()
teacher1 = Teacher()
```


Now the animal class cannot be instantiated. Let's instantiate it to see the result.

```sh
student1 = Student()
TypeError: Can't instantiate abstract class Student with abstract method contact
```

To make the error go away, we have to add the contact() method to the student and Teacher class as shown below.

```py
from abc import ABC, abstractmethod
class Person(ABC): 

  @abstractmethod
  def contact(self): 
    print("print contact details")

class Student(Person):
    def contact(self): 
        print("print student contact details")
 
class Teacher(Person):
    def contact(self): 
        print("print teacher contact details")
```


### Encapsulation
Encapsulation is the act of enclosing data; you can only use the data in the enclosed unit. While other programming languages perform encapsulation with public, protected, and private methods, Python doesn’t have the same. In Python, we prefix the variable with a single underscore if you need to define a private variable.

```py
class Person:
    def __init__(self):
        self.__name = 'john doe'
```

For a protected variable, you prefix with a single underscore.
```py
class Person:
    def __init__(self):
        self._name = 'john doe'
```

Conclusion
This tutorial has covered how to write Python classes from scratch, and how to define methods and object-oriented programming in Python. These are the fundamental concepts will make you a great programmer.