Classes and Objects

Definition
  • A Class is a user-defined blueprint or prototype from which objects are created. Classes provide a means of bundling data and functionality together.
  • Each class instance or Object can have attributes attached to it for maintaining its state. Objects can also have methods (defined by their class) for modifying their state.
  • A class is like a blueprint while an instance is a copy of the class with actual values
Properties
  • Classes are created by keyword class.
  • Attributes are the variables that belong to a class.
  • Attributes are always public and can be accessed using the dot (.) operator.
An object consists of
  • State: It is represented by the attributes of an object. It also reflects the properties of an object.
  • Behavior: It is represented by the methods of an object. It also reflects the response of an object to other objects.
  • Identity: It gives a unique name to an object and enables one object to interact with other objects.

    class Dog:
        def __init__(self, name): # constructor
            self.name = name # class attribute
        def fun(self): # class method
            print("My name is", self.name)
    Rodger = Dog("Rogder") # Object instantiation
    print(Rodger.name) # Rogder
    Rodger.fun() # My name is Rogder
        

self

  • Class methods must have an extra first parameter in the method definition. We do not give a value for this parameter when we call the method, Python provides it.
  • If we have a method that takes no arguments, then we still have to have one argument (ie self)
  • This is similar to this pointer in C++ and this reference in Java.


  • When we call a method of this object as myobject.method(arg1, arg2), this is automatically converted by Python into MyClass.method(myobject, arg1, arg2)

__init__ ()

  • The __init__ method is similar to constructors in C++ and Java. Constructors are used to initializing the object's state.
  • It runs as soon as an object of a class is instantiated. The method is useful to do any initialization you want to do with your object.
  • Constructor can be Default (no parameters except self) or Parameterized (multiple parameters)

__del__ ()

  • The __del__ method is similar to destructors in C++ and Java. It is called when all references to the object have been deleted i.e when an object is garbage collected.
  • In Python, destructors are not needed as much as in C++ because Python has a garbage collector that handles memory management automatically.

Class and instance variable

  • Instance variables are variables whose value is assigned inside a constructor or method with self whereas class variables are variables whose value is assigned in the class.

    class Dog:
        animal = "dog"
        def __init__(self, name): # set instance variable through constructor
            self.name = name # class attribute
        def fun(self): # class method
            print("My name is", self.name)
        def changeName(self, name):
            self.name = name # change instance variable through class method
        def __del__(self):
            print("destructing object")
    Rodger = Dog("Rogder") # Object instantiation
    Rodger.fun() # My name is Rodger
    Rodger.changeName("Sam")
    Rodger.fun() # My name is Sam
        

Inheritance

  • "object" class is root of all classes. So, even when a class is inheriting from no other class, it is actually inheriting from "object" class.
  • If you forget to invoke the __init__() of the parent class then its instance variables would not be available to the child class.
  • Private members of parent class are not accessible to the child class.

    class Person(object): # parent class
        def __init__(self, name):
            self.name = name
        def display(self):
            print(self.name)

    class Employee(Person):	# child class
        def __init__(self, name, designation):
            self.designation = designation
            Person.__init__(self, name) # invoking the __init__ of the parent class

    a = Employee("abc", "Intern") # creation of an object variable or an instance
    a.display() # calling a function of the class Person using its instance
        

Different forms of inheritance

There are 2 forms of Inheritance
  1. Single inheritance: When a child class inherits from only one parent class.
  2. Multiple inheritance: When a child class inherits from multiple parent classes.
  3. Multilevel inheritance: When we have a child and grandchild relationship.
  4. Hierarchical inheritance: More than one derived classes are created from a single base.
  5. Hybrid inheritance: This form combines more than one form of inheritance.

    '''
    Single Inheritance
    P1
    |
    C1
    '''
    class C1(P1): #
        pass

    '''
    Multiple Inheritance
    P1    P2
      \  /
       C1
    '''
    class C1(P1, P2)
        pass

    '''
    Multilevel Inheritance
    G1
    |
    P1
    |
    C1
    '''
    class P1(G1):
        pass
    class C1(P1):
        pass

    '''
    Hierarchical Inheritance
        P1
      /  |  \
    C1  C2  C3
    '''
    class C1(P1):
        pass
    class C2(P1):
        pass
    class C3(P1):
        pass
        

Encapsulation

Encapsulation is the idea of wrapping data and the methods that work on data within one unit. This puts restrictions on accessing variables and methods directly and can prevent the accidental modification of data

Types of Members

  • Protected Members: cannot be accessed outside the class but can be accessed from within the class and its subclasses. Prefix the name of the member by a single underscore "_"
  • Private Members: are similar to protect except they should neither be accessed outside the class nor by any subclass. Prefix the name of the member by double underscore "__"
  • Although the protected variable can be accessed out of the class as well as in the derived class(modified too in derived class), it is customary(convention not a rule) to not access the protected out the class body.
example 🜜

Polymorphism

Polymorphism means the same function name (but different signatures) being used for different types.

    ''' Inbuilt Polymorphism of len() in Python '''
    print(len("geeks")) # for string
    print(len([10, 20, 30])) # for a list

    ''' User-defined Polymorphism '''
    def add(x, y, z = 0):
        return x + y+z
    print(add(2, 3)) # 5
    print(add(2, 3, 4)) # 9

    ''' User-defined Polymorphism with Class methods '''
    class India():
        def capital(self):
            print("New Delhi")

    class USA():
        def capital(self):
            print("Washington, D.C.")

    obj_ind = India()
    obj_usa = USA()
    obj_ind.capital() # New Delhi
    obj_usa.capital() # Washington, D.C.

    ''' Polymorphism with a Function and objects '''
    def func(obj):
        obj.capital()

    func(obj_ind) # New Delhi
    func(obj_usa) # Washington, D.C.

    ''' Polymorphism with Inheritance '''
    class Bird:
      def intro(self):
        print("There are many types of birds.")
      def flight(self):
        print("Most of the birds can fly but some cannot.")

    class sparrow(Bird):
      def flight(self):
        print("Sparrows can fly.")

    class ostrich(Bird):
      def flight(self):
        print("Ostriches cannot fly.")

    obj_bird = Bird()
    obj_spr = sparrow()
    obj_ost = ostrich()

    obj_bird.intro() # There are many types of birds.
    obj_bird.flight() # Most of the birds can fly but some cannot.

    obj_spr.intro() # There are many types of birds.
    obj_spr.flight() # Sparrows can fly.

    obj_ost.intro() # There are many types of birds.
    obj_ost.flight() # Ostriches cannot fly.
        

Class or Static Variables

  • All objects of a class share class or static variables.
  • An instance or non-static variables are different for different objects (every object has a copy)

    class CSStudent:
        stream = 'cse' # Class Variable
        def __init__(self, name):
            self.name = name # Instance Variable
    a = CSStudent('abc')
    b = CSStudent('bcd')

    # class variable
    CSStudent.stream == a.stream and a.stream == b.stream # True # all 3 are "cse"

    # changing value of class variable for one of the objects
    a.stream = "ece"
    print(a.stream) # "ece"
    print(b.stream) # "cse"

    # changing value of class variable of all the objects at once
    CSStudent.steam = "it"
    print(a.stream) # "ece" - as it was explicitly changed earlier
    print(b.stream) # "it"

        

Class v/s Static Methods

Property Class method Static method
Definition bound to the class and not the object of the class.
Decorator @classmethod @staticmethod
Access/Modify Class state Can do Can't do
Any specific parameters cls as first parameter NA
General Usage to create factory methods. Factory methods return class objects ( similar to a constructor ) for different use cases. to create utility functions.

    class Variable:
        def __init__(self, name):
            self.name = name

      @classmethod
      def fromIncremented(cls, name, id):
        return cls(fname + "_" + id)

      @staticmethod
      def isValid(name):
        return name[0] != "_"

    var1 = Person('abc')
    var2 = Variable.fromIncremented('abc', 1)

    print (Person.isValid("_123"))
        

Method Overloading and Overriding

  • When the method of child class overrides the method of parent class on being called by the object of child class, it is known as Method Overriding.
  • Method Overloading is the ability of a function or an operator to behave in different ways based on the parameters or the operands respectively.

Abstract Method and Abstract Class

  • An Abstract method is a method that is declared, but contains no implementation.
  • An Abstract class is a class that contains one or more abstract methods.
  • An Abstract class may not be instantiated, and its abstract method must be implemented by the base class.
  • Python provides a module named ABC which provides the base for defining Abstract Base Class (ABC).

    from abc import ABC, abstractmethod
    class Mother:
        @abstractmethod
        def absmethod(self):
            pass
        def defined(self):
            print("this is a method that is defined in the ABC")
    class Daughter(Mother):
        def absmethod(self):
            print("Abstract method implemented in the child class")