Object Oriented Programming in Python
Published: 2023-05-11 05:42:53

## Introduction of OOPs in Python
Python supports object-oriented programming through the use of classes, objects, attributes, and methods. These concepts allow you to create modular and reusable code that is easier to understand and maintain.

Everything in Python is an object. Using Pythom we can create classes and objects.

##  Classes
1. Class is like a blueprint of an real object. Classes defines object features 
and attributes which help to create objects.
3. Class comprises of properties and behaviour.

	For eg:- Consider a class Person, it might have properties or behaviour of person. Those properties might be name, age, etc.
4.  How to make a _Class_ in Python. We use _Class_ keyword.

```
	class Person:
	    def __init__(self, name, age):
	        self.name = name
	        self.age = age

	def say_hello(self):
	        print(f"Hello, my name is {self.name} and I am {self.age} years old.")
person = Person("John", 30)
```
## Objects
Objects is real world entity which contains all features defines in class. It is instance of their class.

This may be any real-world object. 
For eg:- In case of a class Person, we can have real world objects john, ankit etc.
```
john = Person("John", 30)
ankit = Person("ankit", 25)
```
1. Objects consists of  attributes/properties .
2. It consists of  behaviour represented by the methods of an object.
3. Identity  gives a unique name to an object.


## Inheritance
Inheritance is one of the key features of object-oriented programming, and Python supports it as well. Inheritance allows you to create a new class that is a modified version of an existing class, inheriting all the attributes and methods of the parent class.
For eg:- In this example, we define a new class called `Student` that inherits from the `Person` class
```
class Student(Person):
    def __init__(self, name, age, grade):
        super().__init__(name, age)
        self.grade = grade

    def say_hello(self):
        super().say_hello()
        print(f"I am a student in grade {self.grade}.")

```
#### Types of Inheritance:- 
__1. Single level Inheritance__:  
Single-level inheritance enables a derived class to inherit characteristics from a single-parent class.
example :-
```
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def say_hello(self):
        print(f"Hello, my name is {self.name} and I am {self.age} years old.")

class Student(Person):
    def __init__(self, name, age, grade):
        super().__init__(name, age)
        self.grade = grade
my_student = Student("John", 18, "12th")
my_student.say_hello()
#output: Hello, my name is John and I am 18 years old.
```
__2. Multilevel Inheritance__ :
Multi-level inheritance enables a derived class to inherit properties from an immediate parent class which in turn inherits properties from his parent class.
example :-
```
class Animal:
    def __init__(self, name):
        self.name = name

    def eat(self):
        print(f"{self.name} is eating.")

class Mammal(Animal):
    def walk(self):
        print(f"{self.name} is walking.")

class Dog(Mammal):
    def bark(self):
        print(f"{self.name} is barking.")

class LabradorRetriever(Dog):
    def fetch(self):
        print(f"{self.name} is fetching.")

my_dog = LabradorRetriever("Buddy")
# output
my_dog.eat()    # Buddy is eating.
my_dog.walk()   # Buddy is walking.
my_dog.bark()   # Buddy is barking.
my_dog.fetch()  # Buddy is fetching.
```

__3. Hierarchical Inheritance__:  
Hierarchical inheritance in Python involves creating a class hierarchy where multiple subclasses inherit from the same parent class. In other words, it's a structure where one parent class has multiple child classes.
example :-
```
 # Base class
 class  Animal: 
 def  __init__(self, name):
	  self.name = name 
 def  speak(self):
	  pass
 # Subclass 1
 class  Dog(Animal):
	  def  speak(self):
		   print(f"{self.name} says woof.")
 #Subclass 2
 class  Cow(Animal):
	 def  speak(self):
		 print(f"{self.name} says moo.")
my_dog = Dog("Buddy")
my_cow = Cow("MooMoo")
 # output
my_dog.speak()  # Buddy says woof.
my_cow.speak()  # MooMoo says moo.
```
__4.Multiple Inheritance__: 
Multiple level inheritance enables one derived class to inherit properties from more than one base class.
example :-
```
class  Animal: 
	def  __init__(self, name, species): 
		self.name = name 
		self.species = species 
class  Carnivore: 
	def  __init__(self): 
		self.is_carnivore = True  
	def  eat(self): 
		print("I eat meat.")

class  Cat(Animal, Carnivore): 
	def  __init__(self, name): 
		Animal.__init__(self, name, species="Cat") 
		Carnivore.__init__(self)  
		self.sound = "Meow"

cat1 = Cat("Luna")
print(cat1.name, cat1.species, cat1.sound, cat1.is_carnivore)
 #output: Luna Cat Meow True
```


## Polymorphism
polymorphism in Python allows you to write code that works with objects of different classes through a shared interface. This can make your code more flexible and reusable, and allow you to take advantage of inheritance and other OOP features.
example :-
```
class Animal:
    def __init__(self, name):
        self.name = name

    def make_sound(self):
        pass

class Dog(Animal):
    # override methods
    def make_sound(self):
        print(f"{self.name} says woof.")

class Cat(Animal):
	# override methods
    def make_sound(self):
        print(f"{self.name} says meow.")

my_dog = Dog("Buddy")
my_cat = Cat("Kitty")
 #output
 my_dog.make_sound()  # Buddy says woof.
my_cat.make_sound()  # Kitty says meow.
```
## Encapsulation
Encapsulation in Python allows you to hide the implementation details of an object from the outside world, and expose only the necessary information through a well-defined interface. This can help to achieve data security, modularity, and maintainability of code.
example :-
```
class Person:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age

    def get_name(self):
        return self.__name

    def set_name(self, name):
        self.__name = name

    def get_age(self):
        return self.__age

    def set_age(self, age):
        if age < 0:
            raise ValueError("Age cannot be negative.")
        self.__age = age

person = Person("Alice", 30)
print(person.get_name())  # Alice
person.set_age(35)
print(person.get_age())  # 35
```
## Abstraction
Abstraction is a concept in object-oriented programming that refers to the ability to focus on the essential features of an object or system, while ignoring its less important or irrelevant details. In other words, it allows us to represent complex real-world entities in a simplified and manageable way.
For example :- We define a concrete class `ConcreteClass` that subclasses `AbstractClass` and provides implementations of its abstract methods `method1` and `method2`. These implementations provide concrete functionality for the abstract methods, and allow us to use the `ConcreteClass` as a fully functional class.
```
 # abstractclass
 import abc
class AbstractClass(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def method1(self):
        pass
    @abc.abstractmethod
    def method2(self):
        pass
  # Inherit abstract class
class ConcreteClass(AbstractClass):
    def method1(self):
        print("Implementation of method1")
    def method2(self):
        print("Implementation of method2")
 # creating object
obj = ConcreteClass()
obj.method1()
obj.method2()
 # output
 Implementation of method1
Implementation of method2
```
## References
[GeeksforGeeks](https://www.geeksforgeeks.org/python-oops-concepts/)
[W3Schools](https://www.w3schools.com/python/python_classes.asp)
[freecodecamp](https://www.freecodecamp.org/news/crash-course-object-oriented-programming-in-python/)