Garbage Collector in Python

Languages like C++, the programmer is responsible for both the creation and destruction of objects, usually, the programmer takes very much care while creating objects but neglecting destruction of useless objects, because of his negligence, total memory may be filled with useless objects, which creates a memory problem and total application will become down with this memory problem.

But in python, we have some assistant which is always running in the background to destroy useless objects, because of this assistant the chance of failing python with memory problems will be very less, and this assistant is nothing but a garbage collector.

The main objective of the garbage collector is to destroy useless objects.

When we say an object is eligible for garbage collection

  • If an object does not contain any reference variable, then only it is responsible for garbage collection.
  • If the reference count is zero, then only the object is eligible for garbage collection.

HowTo Enable and Disable Garbage Collector in our Program

By Default, the Garbage Collector is enabled in our programs if we don't want we can disable it.

To check whether it is enabled or disabled python provides one module called GC Module.

The GC Module contains three Functionalities to check :

  • gc.isenabled()
  • gc.isdiable()
  • gc.enable()

Example: By Default, the gc module is enabled, and hence python returns True

importing-gc-enabled

Example: Let me disable the gc Module and print gc.isenabled

disable-gc-module

if Statement

Destructor in python

A Destructor is a special Method in python to perform the cleanup activities, which means resource deallocation activities.

The name of the destructor should be __del__(), Just before destroying an object garbage collector always calls destructor to perform the cleanup activities(Like Resource de-allocation, closing database collection, etc..)

Once the destruction execution completes, then the garbage collector automatically destroys the object.

The Main Purpose of the destructor is to clean up the object and not to destroy the objects, the destroying object will be taken care of by the python virtual machine.

The following example demonstrates the use of destructor

import time
class Test:
    def __init__(self):
        print("Object Initialization Activities...")
        
    def __del__(self):
        print("Fulfilling Last wish and performing object cleanup activities..")
        
t=Test()
t=None
time.sleep(10)
print("End of Application..")

The output is:

use-of destructor

If you feel that memory problem may come in the middle of program execution then you can assign that object to none.

import time
class Test:
    def __init__(self):
        print("Object Initialization Activities...")
        
    def __del__(self):
        print("Fulfilling Last wish and performing object cleanup activities..")
        
t1=Test()
t2=Test()
t1=None
t2=None
print("End of Application..")   

The output is:

assigning-an-object-to-none-in-the-middle-of-the-program

If you observe the above output first, the object will be executed and then destructor, and at last end of the application will come.

The following example is to demonstrate, If the object does not contain any reference variable then only it is eligible for garbage collection, ie, if the reference count is zero, then the only object is eligible for garbage collection.

import time
class Test:
    def __init__(self):
        print("Constructor Execution...")
        
    def __del__(self):
        print("Destructor Execution...")
        
t1=Test()
t2=t1
t3=t1
del t1
time.sleep(10)
print("object not dectroyed after deleting t1")
del t2
time.sleep(10)
print("Object not destroyed even after deleting t2")
time.sleep(10)
print("Deleting Last Reference..")
del t3
print("End of application..")

The output is:

if-reference-count-is-zero-then-object-is-eligible-for-garbage-collection

The following example demonstrates the destruction of list objects

import time
class Test:
    def __init__(self):
        print("Constructor Execution...")
        
    def __del__(self):
        print("Destructor Execution...")
        
l=[Test(),Test(),Test()]
print("Making List Object eligible for garbage collection..")
del l
time.sleep(10)
print("End of Application")

The output is:

destroying-list-object

The 3 Important Interview Questions

Q.1.What is the difference between del t1 and t1=none

Whenever we don't want object reference, then you can use del t, then t will be deleted and automatically the corresponding object also deleted, and python calls the destructor and object also got destroyed.

Now, if you try to access it, then Python will throw an error

class Test:
    def __init__(self):
        print("Constructor Execution...")
        
    def __del__(self):
        print("Destructor Execution...")
        
t=Test()
del t
print("End of Application")
print(t)

The output is:

difference-between-delt-and-t-none

We can use t=None only when we want to use the reference variable in the future.

In this case initially, we created an object t=Test() by using t as a reference variable, now we are reassigningt=None and t is now pointing to None object and hence reference object won't be deleted the only object got deleted.

class Test:
    def __init__(self):
        print("Constructor Execution...")
        
    def __del__(self):
        print("Destructor Execution...")
t=Test()
t=None
print("End of Application")
print(t)

The output is:

t-none-object

So If we don't want reference variable and object then we can use del t and if we don't want a reference object but we want only the reference variable then we can use t=None

Q.2.How to Find the Number of Reference of an Object

The Python provides a sys Module, which contains a functionality called sys.getrefcount function, by using this we can find the number of reference objects.

And also by default python virtual machine provides one reference variable called self

import sys
class Test:
    pass
t1=Test()
t2=t1
t3=t2
t4=t3
print(sys.getrefcount(t1))

In the above program, we have created a reference variable and one default variable also there and hence the total reference count is 5

refrence-count

If we delete any reference variable then

import sys
class Test:
    pass
t1=Test()
t2=t1
t3=t2
t4=t3
del t3,t4
print(sys.getrefcount(t1))

The output is:

deleting-reference-variable

Q.3.What is the difference between constructor and Destructor

Constructor Destructor
The Name of the Constructor should be __init__() The Name of Destructor should be __del__()
The Main Objective of the constructor is to perform initialization activities of an object, initialization means assigning values to the instance variables The main Objective of the Destructors to perform cleanup activities of an object, cleanup activity means resource deallocation like closing database connection, etc..
Just after creating an object pvm will execute constructor automatically to perform initialization activities

Just before destroying an object, the garbage collector will execute destructor automatically to perform to clean up activities

Using Members of one class inside another Class

We can access the members of one class inside the other class in two ways:

  • By using the HAS-A Relationship
  • By using IS-A Relationship

Python String and Declaring String

By Composition(HAS-A Relationship) in python

By using creating an object we can access the members of one class inside another class, this approach is nothing but by composition or HAS-A relationship.

The main advantage of the HAS-A relationship is code reusability.

class Engine:
    def useEngine(self):
        print("Engine Specific functionality..")
        
class Car:
    def __init__(self):
        self.engine=Engine()   ##class car HAS-A refrence to engine by using that refrence we can call engine specific functions
    def useCar(self):
        print("Car required Engine Functionality")
        self.engine.useEngine()
c=Car()
c.useCar()        

The output is:

using-one-class-inside-another-class

Example 2:

class Car:
    def __init__(self,name,model,color):
        self.name=name
        self.model=model
        self.color=color
    def getInfo(self):
        print("Car Name:{}, Model:{},color:{}".format(self.name,self.model,self.color))
        
class Employee:
     def __init__(self,ename,eno,car):
         self.ename=ename
         self.eno=eno
         self.car=car
     def empInfo(self):
         print("Employee Name:", self.ename)
         print("Employee Number:", self.eno)
         print("Employee Car Info:")
         self.car.getInfo()
         
car=Car("Innova","2.5V","Grey")
e=Employee("Chercher",367937,car)
e=e.empInfo()

The output is:

car-model-program

Example 3:

class SportsNews:
    def sportsInfo(self):
        print("Sports Information-1")
        print("Sports Information-2")
        print("Sports Information-3")
        print("Sports Information-4")
        
class MovieNews:
     def moviesInfo(self):
         print("Movies Information-1")
         print("Movies Information-2")
         print("Movies Information-3")
         print("Movies Information-4")
         
class PoliticsNews:
    def politicsInfo(self):
        print("Politics Information-1")
        print("Politics Information-2")
        print("Politics Information-3")
        print("Politics Information-4")

class WonderNews:
    def __init__(self,sports,movies,politics):
        self.sports=SportsNews()
        self.movies=MovieNews()
        self.politics=PoliticsNews()
        
    def getTotalNews(self):
        print("Welcome to Wonder News:")
        self.sports.sportsInfo()
        self.movies.moviesInfo()
        self.politics.politicsInfo()
        
sports=SportsNews()
movies=MovieNews()
politics=PoliticsNews()        
wnews=WonderNews(sports,movies,politics)
wnews.getTotalNews() 

The output is:

sports-movies-politics-news

Concatenation Operator for List

By Inheritance(IS-A Relationship) in python

Inheritance: The Process of deriving an object or class upon another object or class to retain similar implementation is called inheritance.

The Base class is called as Parent class and Derived class is called a child class.

By using the Inheritance method we can access the method of one inside another class this is also known as the IS-A relationship/parent to child relationship.

The parent class members are by default available to child class and hence child class can reuse parent class functionalities without rewriting it(Helps in code reusability).

A child class can define new members also and hence child class can extend the parent class functionality(Code Extendibilty).

The following example demonstrates the use of parent class and child class

class Parent:
    def method1(self):
        print("Parent class Method")
        
class C(Parent):
    def method2(self):
        print("child class Method")

c=C()
c.method1()
c.method2()        

The output is:

use-of-parent-class-and-child-class

Example 2:

class Parent:
    a=10
    def __init__(self):
        print("Parent constructor")
        self.b=20
        
    def method1(self):
        print("Parent instance Method")
        
    @classmethod
    def method2(self):
        print("Parent Class Method")
        
    @staticmethod
    def method3():
        print("Parent Static Method")
        
class Child(Parent):
    pass     

c=Child()
print(c.a)
print(c.b)
c.method1()
c.method2()
c.method3()

The output is:

accessing-parent-class-constructor-through-child-class.

In the above example, the parent class contains constructor, instance method and static method but child class does not contain anything but still able to access from the parent class by Using Inheritance method or IS-A relationship.

Developing Employee and person classes with inheritance

class Person:
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def eatndrink(self):
        print("Eating Food and drinking water")
        
class Employee(Person):
    def __init__(self,name,age,eno,esal):
        self.name=name
        self.age=age
        self.eno=eno
        self.esal=esal

    def work(self):
         print("coding python programs")
         
    def empinfo(self):
         print("Employee Name:", self.name)
         print("Employee Age:", self.age)
         print("Employee Number:", self.eno)
         print("Employee Salary:", self.esal)
         
e=Employee("Ashwini",27,567438,50000)
e.eatndrink()
e.work()
e.empinfo()         

The output is:

eating-and-drinking-parent-and-child-class

super() function.

class Person:
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def eatndrink(self):
        print("Eating Food and drinking water")
        
class Employee(Person):
    def __init__(self,name,age,eno,esal):
/* we are calling parent class from child class by passing the name and age as arguments and hence parent class constructor is now responsible to create name and age for the employee */
        super().__init__(name,age) 
        self.eno=eno                                               
        self.esal=esal

    def work(self):
         print("coding python programs")
         
    def empinfo(self):
         print("Employee Name:", self.name)
         print("Employee Age:", self.age)
         print("Employee Number:", self.eno)
         print("Employee Salary:", self.esal)
         
e=Employee("Ashwini",27,567438,50000)
e.eatndrink()
e.work()
e.empinfo()         
         

The output is:

accessing-parent-class-constructor-from-child-class

Python Tuple Datatype

The Difference between IS-A and HAS-A Relationship

IS-A Relationship HAS-A Relationship
If we want to extend existing functionality with some more extra functionality then we should go for IS-A relationship If we don't want to extend and just we have to use existing functionality then we should go for HAS-A relationship/composition

The following example demonstrates the IS-a and HAS-A relationship

class Car:
    def __init__(self,name,model,color):
        self.name=name
        self.model=model
        self.color=color
        
    def getinfo(self):
        print("	Car Name:{}
	Model:{}
	Color:{}".format(self.name,self.model,self.color))
        
class Person:
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def eatndrink(self):
        print("Eating food and drinking water")

class Employee(Person):
    def __init__(self,name,age,eno,esal,car):
        super().__init__(name,age)
        self.eno=eno
        self.esal=esal
        self.car=car
        
    def work(self):
        print("Coding Python Program..")
        
    def empinfo(self):
        print("Employee Name:",self.name)
        print("Employee Age:", self.age)
        print("Employee Number:", self.eno)
        print("Employee Salary:", self.esal)
        print("Employee Car Iformation:")
        self.car.getinfo()  ##employee using car functionality
        
car=Car("Innova","2.5V","Grey")
e=Employee("Ashwini",27,567438, 50000,car)
e.eatndrink()    ##Employee using person class functionality
e.work()
e.empinfo()    

The Output is:

employee-details-and-car-details

The difference Between Composition and Aggregation

Composition Aggregation
Without an existing container object if there is no chance of existing contained objects then container and contained objects are strongly associated with each other, this strong association is called as Composition Without an existing container object if there is a chance of existing contained objects then container and contained objects are weakly associated with each other, this weak association is called Aggregation

Example: University contains several Departments. without existing University there no chance of existing Department object. And Hence university and Department are strongly associated with each other this strong bonding is nothing but a composition.

Example: Several Professors may work in the Department. without the existing department still, there may be a chance of an existing professor. Hence Department and professors are weakly associated with each other, this weak association is called Aggregation.

class University:

def __init__(self):

self.department=self.Department()

class Department:

pass

u=University()

class Professor:

pass

class Department:

def __init__(self,professor):

self.professor=professor

professor=professor()

csdept=Department(Professor)

itdept=Department(professor)

In Composition, objects are strongly associated

In Aggregation, objects are weakly associated

In Composition container objects hold directly contained objects

In Aggregation container objects just holds just reference of contained objects

Python None Datatype

Types of Inheritance in python

Python provides 6 types of inheritance process:

Single Inheritance in python

The process of inheriting members from one class to another class is known as a single inheritance

Single inheritance include only one parent and one child class

Example:

class Parent:
    def method1(self):
        print("Parent Method")
        
class Child(Parent):
    def method2(self):
        print("Child Method")
        
c=Child()
c.method1()
c.method2()        

The output is:

single-inheritance

Multi-Level Inheritance in python

The Process of inheriting members from multiple classes to single class one after the other is known as multi-level inheritance.

Example:

class Parent:
    def method1(self):
        print("Parent Method")
        
class Child(Parent):
    def method2(self):
        print("Child Method")
        
class Grandchild(Child):  ## both Parent and child class members are available to grand child class
    def method3(self):
        print("Subchild Method")
        
c=Grandchild() ##Creating an object for grandchild class
c.method1()
c.method2() 
c.method3()    

The output is:

multilevel-inheritance

Python Bytes and Bytearray

Hierarchical Inheritance in python

The process of inheriting members from one parent class to multiple child class which is present at the same level is called hierarchical inheritance.

Example:

class Parent:
    def method1(self):
        print("Parent Method")
        
class Child(Parent):
    def method2(self):
        print("Child1 Method")
        
class Child2(Parent):
    def method3(self):
        print("Child2 Method")
        
c=Child()
c.method1()
c.method2()

The output is:

hierarchical-inheritance-part-one

And, if try to print method3 by using the object reference of c then python will throw an attribute error because there is no relationship between child1 and child2.

We have to create child2 object by using c1 reference and then you can call method3 here

class Parent:
    def method1(self):
        print("Parent Method")
        
class Child(Parent):
    def method2(self):
        print("Child1 Method")
        
class Child2(Parent):
    def method3(self):
        print("Child2 Method")
        
c=Child()
c.method1()
c.method2() 
c1=Child2()
c1.method3()

The output is:

hierarchical-inheritance-part-tw

Python Packages

Multiple Inheritance in python

The Process of inheriting members from multiple parent class to a single child class at a time is called multiple inheritances.

This process includes multiple parent class and single child class.

Example:

class Parent1:
    def method1(self):
        print("Parent1 Method")
        
class Parent2:
    def method2(self):
        print("Parent2 Method")
        
class Child(Parent1,Parent2):
    def method3(self):
        print("Child Method")
        
c=Child()
c.method1()
c.method2()
c.method3()
        

The output is:

multiple-inheritance

Why Java Won't Support Multiple Inheritance

Example:

class P1:
    {
     }
class P2:
{
 }
Class C extends P1,P2    
{
 }

Because of the diamond access problem, java won't support multiple inheritances.

But Python provides a solution to this problem:

If the same method is inherited from both parent classes, then python will always consider the order of the parent classes in the declaration of the child class.

Child(parent1,parent2)===>Parent1 method is considered.

Child(parent2,parent1)==>Parent2 method is considered

Example:

class parent1:
    def method1(self):
        print("Parent1 Method")
        
class Parent2:
    def method1(self):
        print("Parent2 Method")
        
class Child(Parent1,Parent2):
    def method3(self):
        print("Child Method")

c=Child()
c.method1()    

The output is:

python-supports-multiple-inheritance-one

If we change the order of parent class in the child class then while calling the method we will get the parent2.

class parent1:
    def method1(self):
        print("Parent1 Method")
        
class Parent2:
    def method1(self):
        print("Parent2 Method")
        
class Child(Parent2,Parent1):
    def method3(self):
        print("Child Method")

c=Child()
c.method1()    

The output is:

python-supports-multiple-inheritance-two

Hybrid Inheritance

Hybrid is nothing but Mixing or Combination, The Combination of single, multiple, multilevel or hierarchical inheritance is called hybrid inheritance.

In hybrid inheritance, method resolution is based on the MRO(Method Resolution Order) algorithm.

Cyclic Inheritance

The Process of Inheriting members from one class to another class in a cyclic way is called Cyclic inheritance.

Most of the time cyclic inheritance is not required and hence the programming languages like java, python won't support cyclic inheritance.

Purpose of Installing Package

Method Resolution Order in python

The Python Virtual machine will give priority while resolving the methods, The order in which method is going to solve is called a method resolution order.

We can find the method resolution order of any class by using the following syntax:

print(classname.mro())

Every class in python there is one common parent class is there in python which is known as an object class.

Object class act as the root of the python class hierarchy.

Consider the below Image, in that class A contains two child classes class B and class C and class D is having Two-parent Classes class B and class C.

rmo-theme-picture

If you're in class D and if you call method1, then the method resolution order of d is, python first check with D class object if d does not contain Method1 then it will check with class B and if B also does not contain then It will check with class A and if Class A also does not contain then it will check with Object Class.

Example:

class A:
    def method1(self):
        print("A Class Method")
        
class B(A):
    pass
class C(A):
    pass
class D(B,C):
    pass
d=D()
d.method1()

The output is:

d-class-method-resolution

If Object class also does not contain then python will show an attribute error.

class A:
    pass
        
class B(A):
    pass
class C(A):
    pass
class D(B,C):
    pass
d=D()
d.method1()

The output is:

no-class-object-does-not-contain-method

In Hybrid inheritance, the method resolution order is based on the MRO algorithm.

Example: Write the RMO algorithm for the below image.

rmo-example-two

class A:
    pass
        
class B:
    pass
class C:
    pass
class D(A,B):
    pass
class E(B,C):
    pass
class F(D,E,C):
    pass
print(A.mro())
print(B.mro())
print(C.mro())
print(D.mro())
print(E.mro())
print(F.mro())    

The output is:

output-of-rmo-of-two

If all the classes contain method1 and if we try to call method1 from F class then the RMO of F class object is:

class A:
    def method1(self):
        print("A Class Method")
        
class B:
    def method1(self):
        print("B Class Method")
        
class C:
    def method1(self):
        print("C Class Method")
        
class D(A,B):
    def method1(self):
        print("D Class Method")
        
class E(B,C):
    def method1(self):
        print("E Class Method")
        
class F(D,E,C):
    def method1(self):
        print("F Class Method")
        
f=F()
f.method1()        

The output is:

output-of-rmo-of-f-class

Classes in python

MRO Algorithm in python

  • The Method Resolution Order Algorithm is also known as the C3 algorithm
  • Samuele Pedroni proposed this algorithm.
  • It follows DFLR(Dept First Left to Right), ie, the child will get more priority than Parent class and Left parent will get more priority than Right Parent.
  • The formula of the MRO algorithm is: MRO(X)=X+Merge(MRO(P1), MRO(P2),......, parent(List), here we have to consider only the immediate parents.
  • If a Program contains class1,class2,class3, and class4 then class1(First element) is called a Head element and class2,class3,class4 are(Remaining part) called as Tail elements.

How the Merge process will work

  • Take Head of the first list object
  • If the head is not present in the tail part of any other list then add this head element to the result and remove it from all the list.
  • If the head is present in the tail part of any other list, then consider the head element of the next list and continue the same process.
  • Consider the below image and let me explain to you how the merge will work :

rmo-example-two

The Formula is: MRO(X)=X+Merge(MRO(P1), MRO(P2),......, parent(List)

To Find the MRO(F)=F+Merge(MRO(D), MRO(E), MRO(C), DEC),

=F+Merge(DABO, EBCO, CO, EC)

=F+D+Merge(ABO, EBCO, CO, EC)

=F+D+A+E+Merge(BO, BCO, CO, C)

=F+D+A+E+B+Merge(O,CO,CO,C)

=F+D+A+E+B+C+Merge(O, O, O)

=F+D+A+E+B+C+O

=FDAEBCO

Let me check this with practically

class A:
   pass
        
class B:
   pass
        
class C:
    pass
        
class D(A,B):
    pass
        
class E(B,C):
    pass
        
class F(D,E,C):
    pass
        
print(F.mro())        

The output is:

practical-proof-for-the-theory

Example: Find the MRO(F) for the below image

rmo-of-example-three-image

Formula:MRO(X)=X+Merge(MRO(P1), MRO(P2),......, parent(List)

=F+Merge(MRO(D),MRO(E),DE)

=F+Merge(DABO, EACO, DE)

=F+D+Merge(ABO,EACO,E)

=F+D+E+Merge(ABO,ACO)

=F+D+E+A+Merge(BO,CO)

=F+D+E+A+B+Merge(O,CO)

=F+D+E+A+B+C+Merge(O,O)

=F+D+E+A+B+C+O

Let me check this with practically

class A:
   pass
        
class B:
   pass
        
class C:
    pass
        
class D(A,B):
    pass
        
class E(A,C):
    pass
        
class F(D,E):
    pass
        
print(F.mro())       

The output is:

rmo-of-example-three-output

&am

Comment / Suggestion Section
Point our Mistakes and Post Your Suggestions