<jupyter_start><jupyter_text>Object Oriented Programming (Python) It is a programming paradigm - A way to write programs**Class** is a template and Object is **Instance** of that class.*We can have multiple instances of a class*<jupyter_code>class Student:  # Class named Student
    pass
s1 = Student() # s1 is instance of "Student" class.
print(type(s1))<jupyter_output><class '__main__.Student'><jupyter_text>**s1 belongs to the class *Student***<jupyter_code>l = list()
print(type(l))<jupyter_output><class 'list'><jupyter_text>*l* belongs to the class list()**Infact everything in python is a class**  Attributes in Class<jupyter_code>s1.name = "Rajat"
s1.__dict__
s2 = Student()
s2.name = 'Rajat'
s2.rollno = '123'<jupyter_output><empty_output><jupyter_text>**name and rollno are attributes(data) of the object s2 of class Student**<jupyter_code>s2.__dict__<jupyter_output><empty_output><jupyter_text>Those attributes are stored as key-value pairs as dict.object.key = value<jupyter_code>hasattr(s1, 'name') # check if s1 have the attribute named "name".
hasattr(s1, 'rollno')
getattr(s1, 'name') # Alternate of object.key
getattr(s2, 'rollno')
getattr(s1, 'rollno')
# dont want an error
getattr(s1, 'rollno', 'not found')
# delete attribute
delattr(s1, 'name')
s1.name
class student:
    name = '<NAME>'
    pp = 80
    
    def isPassed(self):
        self.percentage = 90
        if self.percentage > self.pp:
            return True<jupyter_output><empty_output><jupyter_text>Class and Instance Methods<jupyter_code>class Student:
    passingper = 40
    def StudentDetails(self):  # Instance Method
        self.name = '<NAME>'
        print(self.name)
        
        self.percentage = 96
        print(self.percentage)
        
        pass
    
    def isPassed(self):  # Instance Method
        if self.percentage >= self.passingper: # or can use Student.passingper
            print("passed")
        else:
            print("Not Passed")
s1 = Student()
s1.StudentDetails()
s1.isPassed()<jupyter_output>passed<jupyter_text>**Instance Methods** are those which do require the properties of object and we need to pass the object itself to them.  Static MethosThese methods are which which do not require properties from the object itself so we do not need to pass 'self' to them. But this can be done by **@staticmethod** decorator. this decorator ignores the first argument from the method.<jupyter_code>class Student:
    passingper = 40
    name = 'my name'
    def StudentDetails(self):  # Instance Method
        self.name = '<NAME>'
        print(self.name)
        
        self.percentage = 96
        print(self.percentage)
        
        pass
    
    def isPassed(self):  # Instance Method
        if self.percentage >= self.passingper: # or can use Student.passingper
            print("passed")
        else:
            print("Not Passed")
            
    ## Static Method
    @staticmethod  # this ignores the first argument in the function
    def welcometoschool():
        print('Welcome to School')
s1 = Student()
s1.welcometoschool()
s1.name
s1.StudentDetails() # name updated by the method
s1.name<jupyter_output><empty_output><jupyter_text>"__init__ method"<jupyter_code>class Student:
    
    def __init__(self, name, rollno):
        self.name = name
        self.rollno = rollno
        
    passingper = 40
    name = '<NAME>'
    def StudentDetails(self):  # Instance Method
        self.name = '<NAME>'
        print(self.name)
        
        self.percentage = 96
        print(self.percentage)
        
        pass
    
    def isPassed(self):  # Instance Method
        if self.percentage >= self.passingper: # or can use Student.passingper
            print("passed")
        else:
            print("Not Passed")
            
    ## Static Method
    @staticmethod  # this ignores the first argument in the function
    def welcometoschool():
        print('Welcome to School')
s1 = Student('Rajat', 123)<jupyter_output><empty_output><jupyter_text>Classmethods**Classmethods** manipulates the input and return an object of any class.<jupyter_code>from datetime import date
class Student:
    
    def __init__(self, name, age, rollno):
        self.name = name
        self.age = age
        self.rollno = rollno
    
    # classmethod
    @classmethod
    def fromBithYear(cls, name, year, rollno):
        return cls(name, date.today().year - year, rollno)
    
    

    def StudentDetails(self):  # Instance Method
        self.name = '<NAME>'
        print(self.name)
        print("age", self.age)
        self.percentage = 96
        print(self.percentage)
        
        pass
    
    def isPassed(self):  # Instance Method
        if self.percentage >= self.passingper: # or can use Student.passingper
            print("passed")
        else:
            print("Not Passed")
            
    ## Static Method
    @staticmethod  # this ignores the first argument in the function
    def welcometoschool():
        print('Welcome to School')
s1 = Student.fromBithYear('Rajat', 2001, 25)
s1.StudentDetails()<jupyter_output><NAME>
age 20
96<jupyter_text>Public and Private Modifiers<jupyter_code>from datetime import date
class Student:
    __passingpercentage = 40 # private Variable
    
    def __init__(self, name,age, rollno):
        self.__name = name
        self.age = age
        self.rollno = rollno
    
    # classmethod
    @classmethod
    def fromBithYear(cls, name, year, rollno):
        return cls(name, date.today().year - year, rollno)
    
    

    def StudentDetails(self):  # Instance Method
        print(self.__name)
        print("age", self.age)
        self.percentage = 96
        print(self.percentage)
        
        pass
    
    def isPassed(self):  # Instance Method
        if self.percentage >= self.passingper: # or can use Student.passingper
            print("passed")
        else:
            print("Not Passed")
            
    ## Static Method
    @staticmethod  # this ignores the first argument in the function
    def welcometoschool():
        print('Welcome to School')
s1 = Student('Rajat', 20, 251)
s1.StudentDetails()
s1.name = "Rohan"
s1.name
s1.__name
# access outside 
# called Name Bangling Syntax: object._className__varName

s1._Student__name
class Fraction:
    
    def __init__(self, num = 0, den = 1):
        if den == 0:
            # Thorow error
            den = 1
        self.num = num
        self.den = den
        
    def print(self):
        if self.num == 0:
            print(0)
        elif self.den == 1:
            print(self.num)
        else:
            print(self.num, "/" , self.den)
    def simplyfy(self):
        if self.num == 0:
            self.den = 1
            return
        curr = min(self.num, self.den)
        while curr > 1:
            if self.num % curr == 0 and self.den % curr == 0:
                break
            curr -= 1
        self.num = self.num // curr
        self.den = self.den // curr
        
    def add(self, other):
        newNum = other.den * self.num + other.num * self.den
        newDen = other.den * self.den
        
        self.num = newNum
        self.den = newDen
        
        self.simplyfy()
        
    def multiply(self, other):
        self.num = other.num * self.num
        self.den = other.den * self.den
        
        self.simplyfy
        
        
        
        
    
f1 = Fraction(2,3)
f2 = Fraction(1,3)
f1.add(f2)
f1.print()
f1.multiply(f2)
f1.print()
f.__dict__
f.print()
f.simplyfy()
f.print()<jupyter_output>0<jupyter_text>Complex Numbers<jupyter_code>class Complex:
    def __init__(self, a = 0, b = 0):
        self.real = a
        self.im = b
        print(self.real, "+ i" , self.im, sep = "")
        
    def print(self):
        print(self.real, "+ i" , self.im, sep = "")
        
    def plus(self, other, inplace = False):
        a = self.real + other.real
        b = self.im + other.im
        if inplace == True:
            self.real = a
            self.im = b
            self.print()
        else:
            return (Complex(a,b))
            
        
    def mul(self, other):
        a = (self.real* other.real) - (self.im * other.im)
        b = (self.real * other.im) + (self.im * other.real)
        self.real = a
        self.im = b
        self.print()
        
c1 = Complex(4,5)
c2 = Complex(6,7)

n = c1.plus(c2, inplace = True)
c1.print()
c1.mul(c2)
n.print()<jupyter_output>10+ i12
{'fraction_non_alphanumeric': 0.1080440706077479, 'fraction_numerical': 0.014571733206966, 'mean_word_length': 2.2607184241019698, 'pattern_counts': {'":': 0, '<': 48, '<?xml version=': 0, '>': 55, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '11', 'dir': 'jupyter-structured-clean-dedup', 'id': '576422', 'license': 'MIT', 'n_tokens_mistral': 2764, 'n_tokens_neox': 2683, 'n_words': 717, 'path': 'CN DSA/oops-1.ipynb', 'repo_name': 'sonuyadav2/100daysofDSA', 'size': '8478'}