OOPs

 6 pillers:


Every object has a class:

Class

Definition of class:
Class is a blueprint of object

  

Diagram of a class:



- = private

+ = public


Object

Definition of Object :
Object is an instance of the class


Function vs Method?

Fuction = can be defined and called independently



Example :

string, list, dictionary -> anyone can use len() function

Method = function inside the class


Only list can use append() function, because append() method is defined under list class.

Constructor : called when obj of a class is created

In python, constructor = __init__ method

Self : It is the object itself.

  • __init__: Constructor method

  • self.pin, self.balance: Instance variables / Object attributes



Magic methods in python : starts and end with '__' . They are called automatically. We can't create our own magic methods, they are predefined, but we can modify the existing magic method for our custom logic in our class.

Example :
__init__ -> called when obj of a class is created.

__str__ -> called when print() is executed

__add__ -> called when + is found

We can modify it in our class:





Look carefully,

What is self here? 
It will always point to latest/current object.

Now here,

What if you don't pass self?





When the function is passed, the corresponding obj is always passed as an argument. So, it is mandatory to write self in function parameters.

Let's say, below code is written inside the class.

Observe that, instead of calling method directly, we are calling it via object (self).
why? because, method inside class cannot call each other directly, methods are accessible via object only.

Encapsulation


What if someone changes the value of your instance variables, such as pin or balance.



so, you need to keep them private, In jave/c/c++ private self.balance
but, in python, you use '__' , so, self.__balance and you have to write it like their during entire class, __ is important.

same for method as well:
def __menu():
    return



Notice that in the picture, after sbi. there is no visibility for private variables and methods.


Concern : 


So, why this?

First, lets understand that, here when some variables are crucial, then, we make it private so that its value doesn't get change by someone else during code. but, there can be a case that, on purposely, for some functionality, you need to use this variable, so that's why python provides this functionality, so, its like a mutual agreement between developers that ok '__xxx' is a private method/dev, dont use it randomly anywhere.

Getter Setter Methods:


Now, there is this confusion, that i was able to change variable's value directly, so whats the purpose of making it private and then accessing it via setter method?
The answer is, you can control the logic inside function, lets say verified user, pin must be 4 digits etc.




So, this entire thing is called Encapsulation : 


class diagram:
-class name
-variables
-methods


- = private
+ = public

sbi=Atm()
hdfc=Atm()

here sbi, hdfc are called as 'reference variables'




By default, its pass by reference for class objects.


so, class objects are also mutable just like list.




So, this is risky, to avoid this :

The key difference is about mutability and references:

change(l1):

  • Passes the original list object to the function
  • Any modifications inside change() will affect the original list l1
  • Both l1 and the parameter in change() point to the same memory location

change(l1[:]):

  • Passes a shallow copy of the list to the function
  • The original list l1 remains unchanged regardless of what happens inside change()
  • Creates a new list object with the same elements
Note: l1[:] creates a shallow copy, so if your list contains mutable objects (like nested lists), those inner objects can still be modified. For complete protection with nested structures, you'd need copy.deepcopy().

Here's an example showing how shallow copy with l1[:] doesn't protect nested mutable objects:


def change(lst):
    lst[0][0] = 999  # Modify nested list
    lst.append([7, 8])  # Add new element

# Original list with nested lists
l1 = [[1, 2], [3, 4]]

print("Original l1:", l1)  # [[1, 2], [3, 4]]

# Case 1: Pass shallow copy
change(l1[:])
print("After change(l1[:]):", l1)  # [[999, 2], [3, 4]] - nested list modified!

# Reset
l1 = [[1, 2], [3, 4]]

# Case 2: Pass original list
change(l1)
print("After change(l1):", l1)  # [[999, 2], [3, 4], [7, 8]] - both changes applied

Output:

Original l1: [[1, 2], [3, 4]]
After change(l1[:]): [[999, 2], [3, 4]]
After change(l1): [[999, 2], [3, 4], [7, 8]]

What happened:

  • l1[:] creates a new list, but the nested lists [1, 2] and [3, 4] are still the same objects
  • When we modify lst[0][0] = 999, we're changing the contents of the nested list, which affects the original
  • The append() operation only affects the copy, not the original

For complete protection, use deep copy

import copy

def change(lst):
    lst[0][0] = 999
    lst.append([7, 8])

l1 = [[1, 2], [3, 4]]
change(copy.deepcopy(l1))
print(l1)  # [[1, 2], [3, 4]] - completely unchanged

Tuple is immutable.




see, here also, it is pass by reference only, but when you make changes, new copy is created.

Collection:

collection is basically a concept where we store objects in list , tuple etc.

Instance Variable: A variable that belongs to a specific object and has different values for each object. Each object has its own copy. Example: pin, balance (each account has different pin/balance)

Static/Class Variable: A variable that belongs to the class itself and is shared by all objects of that class. All objects see the same value. Example: bank_name, ifsc_code (same for all accounts in that bank)

Key difference: Instance variables are unique per object, while static/class variables are shared across all objects of the same class.




Notice that, the variable 'counter' is placed outside __init__ , but it is inside the class.
Also, it is not self.counter but Atm.counter, which means class name. variable name.


1. No need to pass self in method.
2. Decorator '@staticmethod' --> add when you are dealing with static variables.

Basic Concept: A decorator is a function that takes another function as input and returns a modified version of it.

Example 1:
def my_decorator(func):
    def wrapper():
        print("Before function")
        func()
        print("After function")
    return wrapper

@my_decorator
def say_hello():
    print("Hello!")

say_hello()

Output:

Before function

Hello!
After function
Example 2 : 
def require_auth(func):
    def wrapper():
        if user_logged_in:
            func()
        else:
            print("Please log in first")
    return wrapper

@require_auth
def view_profile():
    print("Viewing profile...")

Relationship between classes :

Above is a UML diagram.



c-c : composition means compulsory.
car must have an engine
department may or may not have students

Memory Tip:

  • Aggregation = "uses" (Department uses Students)
  • Composition = "owns" (Car owns Engine)

Claude does not have the ability to run the code it generates yet.

class diagram for banking system : 


savings acc and curr acc 'is-a' account : inheritance

bank 'has-a' branch : composition


Inheritance


Inheritance :
1. you can inhertit data members, methods and constructor
2. you cannot inherit variables/methods with '__'(private)
method inheritance : 





constructor inheritance :


 





private attribute can't be inherited : 





Polymorphism :




Method overriding :

same method in parent and child class.

    


Method of child class will be executed.

If you want that method of parent should be executed, then, use 'super()'.
- super keyword works inside class only.(that too with constructor and methods only, not with attributes)
- you cannot access it outside the class - s.super().buy() is wrong, no such thing exists.





case where constructor in both class : 



output : 

Example:


o/p : 100, 200

Types of inheritance in python :

    

Multiple inheritance :



buy method is in both class, but it will be executed from the 'Phone' class because it is first in the parameter. - this is MRO rule (method resolution order)

Ex1



Ex2




Polymorphism


This code is valid in c and java.



But, its not valid in python, the area() method will get rewrite.




There is no concrete concept for method overloading in python, but we can make it work by writing some logic.


Operator overloading :


3+4 = 7

'+' works as addition and concatenate


Abstraction
abstract means - hidden

abstract method - there is no code inside it
concrete method - our normal method


1. abc = abstract base class
2. What is abstract method?
it is a method where we want that, if someone is inheriting from my class, he must implement this method in his class.
3. decorator @abstractmethod is compulsory
4. In the class where you use this decorator, it must be inherited from ABC


If you don't implement the method, you will get an error.

what is the need for this?
to maintain code structure.

What is abstract class?
-a class which contains at least 1 abstract method.

YOU CANT CREATE OBJ OF ABSTRACT CLASS.

----------------------------------- END --------------------------------------------


Comments

Popular posts from this blog

Extracting Tables and Text from Images Using Python

Positional Encoding in Transformer

Chain Component in LangChain