OOPs In Python
Tags: python, beginners, class, programming
Published: 2023-02-16 06:11:40

+ It allows users to create objects with their own attributes.
+ Method act as a function that uses some parameter and objects itself or only the object itself to return some value or change the object.
### General format for creating a class
```python

class NameOfClass:  #use camel casing to write the class name.

    def __init__(self, x, y):
        self.x=x
        self.y=y

    def some_method(self):
        pass    

```
### Why use oops?
* It makes the development and management of projects more manageable.
* Feature of data hiding.
* Reusability of code

<hr>


## OOPs concepts
* Class
* Object
* Encapsulation
* Abstraction
* Polymorphism
* Inheritance
* Constructor
* Destructor
  

### Class 
* A class is a blueprint that is used to create an object.
* It creates an object having the same attribute and methods.

```python
class NameOfClass:  #use camel casing to write the class name.

    def __init__(self, x, y):
        self.x=x
        self.y=y

    def some_method(self):
        pass    


```
<hr>

### Object
* It is an entity that has a predefined attributes and method.
```python
class Dog:
    def __init__(self, name):
        self.name=name

    def bark(self):
        print(f'{self.name} woof')

newdog=Dog('tom')
newdog.bark() 
#output- It will print 'tom woof'
```
* All the objects created using class Dog the object will have an attribute name and a Method named bark().
<hr>

### Constructor
* This will create object using the class.
* **__init__** is used to create the object.
```python
class Dog:
    def __init__(self, name):
        self.name=name


newdog=Dog('tom') # New object is constructed with attribute name tom.
```
<hr>

### Destructor
+ It is used to delete the object.
+ It uses **del** Keyword.
```python
class Dog:
    def __init__(self, name):
        self.name=name


newdog=Dog('tom') # New object is constructed with attribute name tom.
del newdog # the object new dog will be deleted.
```

### Encapsulation

+ Encapsulation allows us to hide the object's attributes and method from outside access.
+ Python doesn't have truly private or protected attributes and methods.
+ Attributes and methods that needed to be protected start with single underscore: _
+ Attributes and methods that needed to be private start with double underscore: __

```py
class Person:

    def __init__(self, name, age):
        self.__name = name      # private attribute
        self._age = age         # protected attribute

    def get(self):         # This method is used to get the name that is private.
        return self.__name
    
    def set(self, name):   # This method will take some input names and change the value of the attribute.
        self.__name = name

    def get_age(self):          # This method is used to get the age that is protected.
        return self._age
    
    def set_age(self, age):     # This method will take some input age and change the value of the attribute.
        self._age = age

    def __show(self):           # private method
        return f"My name is {self.__name} and I am {self._age} years old."
    
    def _speak(self):           # protected method
        return f"My name is {self.__name}"

a=Person('Name',5)
print(a._Person__name) # accesing private variable it gives output Name
print(a._age) # acessing private variable it gives output age

a._Person__name='New Name' # change the private variable without using any method.
print(a.get_name())  # it return the name of object

```
<hr>

### Inheritance

+ Inheritance allows us to create a new class(Child class or derived class) based on another class(Parent class or base class).
+ The child class inherits all the attributes and methods of the parent class.
+ In addition, the child class it's own unique attributes and methods.
+ There are several types of inheritance:
   * Single Inheritance: Child class is derived using only one class.
   * Multiple Inheritance: Child class is derived using more than one class.
   * Multilevel Inheritance: In this child, class is inherited from the child class of another parent class
   * Hierarchical Inheritance: Multiple child class is created using single parent class.
   * Hybrid Inheritance: Inheritance of multiple types takes place

```py
class Person:

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

    def show(self):
        print(f'My name is {self.name}, and I am {self.age} years old.')

class NewPerson(Person):						# inheriting parent class in child class

    def speak(self):				# accessing parent class attribute.
        print("I am new person")

class AnotherPerson(Person):						# inheriting parent class in child class

    def __init__(self, name, age, sport):
        super().__init__(name, age)	    # accessing parent class attribute in child class using super and changing the
        self.sport = sport			    # child class attribute 

    def speak(self):
        print(f'My favorite sport is {self.sport}.')

D = AnotherPerson("Bruno", 9, 'Football')
C = NewPerson("Missa", 6)

D.show()		            # accessing parent class method in child class
#output- My name is Bruno, and I am 9 years old.
D.speak()			        # child class accessing its own attributes
#output- My favorite sport is Football. 
C.show()                    # accessing parent class method in child class
#output- My name is Missa, and I am 6 years old.						
C.speak()                   # child class accessing its own attributes
#output- I am new Person			
```
<hr>

### Abstraction
+ Abstraction allows us to hide classes and methods not necessary for the user.
+ An abstract class in python can be made by setting the parent class of any class to ABC after importing it from abc (Abstract Class Bases).
+ If you need to create an abstract method also import abstractmethod from abc module.
+ After that, we can create an abstract method using the @abstractmethod decorator.

```py
from abc import ABC, abstractmethod

class Person(ABC):			# create an abstract class
    @abstractmethod		# create an abstract method
    def speak(self):
        pass

    def show(self):
        print("I am a Person.")

class AnotherPerson(Person):

    def speak(self):
        print("I am another person")


bruno = AnotherPerson()			
bruno.speak()			# ignoring abstract method of abstract class
#output- I am another person
bruno.show()			# non-abstract method of the abstract class still works
#output- I am a Person
person= Person() #this will throw an error abstract class can't be instantiated. 		
```
<hr>

### Polymorphism

+ It simply refers that different classes can have the same method name and it also represents method overriding.
+ Polymorphism in Python can be achieved in two ways: method overriding and duck-typing.

### A. Method Over-riding
Method overriding allows a child class to have a different definition of a method already defined in the parent class.

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

    def speak(self):
        print(f'My name is {self.name} and I am {self.age} years old.')

class NewPerson(Person):
    pass

class AnotherPerson(Person):
    
    def speak(self):				# over-riding method of parent's class
        print(f'Hello! My name is {self.name} and I am {self.age} years old.')

nemo = Person("Nemo", 2)
bruno = AnotherPerson("Bruno", 9)

nemo.speak()						# parent's class method runs
#output- My name is Nemo and I am 2 years old.
bruno.speak()						# child's class method runs
#output- Hello! My name is Bruno and I am 9 years old.
```

### B. Duck Typing
Duck Typing allows different types of classes to have the same method name with its own definition.

```py
class Person:
    
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def speak(self):				# method of first class
        print(f'My name is {self.name} and I am {self.age} years old.')

class AnotherPerson:

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

    def speak(self):				# method of second class with same name
        print(f'Hello! My name is {self.name} and I am {self.age} years old')

nemo =Person("Nemo", 2)
bruno = Another("Bruno", 9)

nemo.speak()						# method of first-class runs
# output- My name is Nemo and I am 2 years old.
bruno.speak()						# method of the second class with the same name runs
#output- Hello! My name is Bruno and I am 9 years old.
```
<hr>

### Dunder/Magic Methods:
+ Dunder or magic methods are special methods used in Python classes.
+ They are used to define how objects of classes behave in case they are used with in-built Python operations.
+ Some commonly used dunder methods are __init__,(self,...), __str__(self), __len__(self), etc.
+ Dunder methods cannot be called, they run automatically when called by in-built python functions.
  
```py
class Book:
    def __init__(self,title,author,page):
        self.title=title
        self.author=author
        self.page=page

    def __str__(self):
        return f'This {self.title} wrote by {self.author}'

    def __len__(self):
        return self.page

kid_book=Book('New Book', 'Myself', 15)

print(kid_book)
#output- This Newbook wrote by Myself
print(len(kid_book))
#output- 15
```
## References
* Geek For Geeks. "Types of inheritance Python". Retrieved from https://www.geeksforgeeks.org/types-of-inheritance-python/
* Geek For Geeks. "self in Python class". Retrieved from https://www.geeksforgeeks.org/self-in-python-class
* Geek For Geeks. "Polymorphism in Python". Retrieved from https://www.geeksforgeeks.org/polymorphism-in-python/?ref=gcse
* Geek For Geeks. "Dunder or magic methods in Python". Retrieved from https://www.geeksforgeeks.org/dunder-magic-methods-python/