UNIT - 3 Python
UNIT - 3 Python
Introduction
A class is a model or plan to create objects. This means, we write a class
with the attributes and actions of objects. Attributes are represented
by variables and actions are performed by methods. So, a class contains variable
and methods. The same variables and methods are also available in the objects
because they are created from the class. These variables are also called
'instance variables' because they are created inside the instance (i.e. object).
Please remember the difference between a function and a method. A
function written inside a class is called a method.Generally, a method is called
using one of the following two ways:
classname.methodname()
instancename.methodname()
The general format of a class is given as follows:
class Classname(object):
attributes
def __init__(self):
def method1():
def method2():
Creating a Class
A class is created with the keyword class and then writing the
Classname. After the Classname, ‘object’ is written inside the Classname. This
object represents the base class name from where all classes in Python are
derived. Even our own classes are also derived from object class. Hence we
should mention ‘object’ in the parenthesis. Writing ‘object’ is not compulsory
The docstring is a string which is written using triple double quotes or
triple single quotes that gives the complete description about the class and its
usage. The docstring is used to create documentation file and it is optional.
‘attributes’ are nothing but variables that contains data. __init__(self) is a
special method to initialize the variables.method1(), method2() etc are methods
that are intended to process variables.
If we take Student class, we can write code in the class that specifies the
attributes and actions performed by any student. For example, a student has
attributes like name, age, marks, etc. these attributes should be written inside the
Student class as variables. Similarly, a student can perform actions like talking,
writing, reading etc. these actions should be represented by methods in the
Student class. So, the class Student contains these attributes and actions, as
shown here:
class Student: #another way is class Student(object):
#the below block defines attributes
def __init__(self):
self.name='Vishnu'
self.age=20
self.marks=900
class Student:
n=10
print(Student.n) OUTPUT
Student.n+=1 10
print(Student.n) 11
s1=Student() 11
print(s1.n) 11
s2=Student()
print(s2.n)
We know that a single copy of variable is shared by all the instances. So, if the
class variable is modified in the class namespace, since same copy of the
variable is modified, the modified copy is available to all the instances.
What happens when the class variable is modified in the instance namespace?
Since every instance will have its own namespace, if the class variable is
modified in one instance namespace, It will not affect the variable in the other
instance namespaces. This is shown in below figure. To access the class
variable at the instance level, we have to create instance first and then refer to
the variable as instancename.variable.
class Student:
n=10
s1 = Student() OUTPUT
print(s1.n) 10
s1.n+=1 11
print(s1.n) 10
s2 = Student()
print(s2.n)
Types of Methods
The purpose of a method is to process the variables provided in the class or in
the method. The variables declared in the class are called class variables (or
static variables) and the variables declared in the constructor are called instance
variables. We can classify the methods in the following 3 types:
1. Instance Methods
(a)Accessor methods
(b)Mutator methods
2. Class Methods
3. Static Methods
Instance Methods
Instance methods are the methods which act upon the instance variables of the
class. Instance methods are bound to instances (or objects) and hence called as:
instancename.method(). Since instance variables are available in the instance,
instance methods need to know the memory address of the instance. This is
provided through 'self' variable by default as first parameter for the instance
method. While calling the instance methods, we need not pass any value to
the 'self' variable.
class Student:
#constructor OUTPUT
def __init__(self,n='',m=0): Number of students2
Enter name: Lakshmi
self.name=n
Enter marks: 100
self.marks=m Hi, I am Lakshmi
#instance method My marks are 100
def display(self): ___________________
print("Hi, I am ",self.name) Enter name: Rahul
Enter marks: 150
print("My marks are ",self.marks)
Hi, I am Rahul
My marks are 150
n=int(input("Number of students")) ___________________
i=0
while(i<n):
name=input("Enter name: ")
marks=input("Enter marks: ")
s1=Student(name,marks)
s1.display()
i+=1
print("___________________")
n=int(input("Number of students"))
i=0
while(i<n):
s=Student()
name=input("Enter name") OUTPUT
Number of students1
s.setName(name)
Enter nameLakshmi
marks=input("Enter marks") Enter marks100
s.setMarks(marks) Hi, I am Lakshmi
Your marks are 100
print("Hi, I am ",s.getName())
print("Your marks are ",s.getMarks())
i+=1
Class Methods
These methods act on class level. Class methods are the methods which act on
the class variables or static variables. These methods are written using
@classmethod decorator above them. By default, the first parameter for class
methods is 'cls' which refers to the class itself. For example, 'cls.var' is the
format to refer to the class variable. These methods are generally called using
the classname.method().
Static Methods
We need static methods when the processing is at the class level but we need
not involve the class or instances. Static methods are used when some
processing is related to the class but does not need the class or its instances to
perform any work.Setting environmental variables, counting the number of
instances of the class or changing an attribute in another class, etc. are the tasks
related to a class. Such tasks are handled by static methods. Also, static methods
can be used to accept some values, process them and return the result. In this
case the involvement of neither the class nor the objects is needed. Static
methods are written with a decorator @staticmethod above them. Static
methods are called in the form of classname.method().
class Myclass:
n=0
def __init__(self):
Myclass.n = Myclass.n+1
@staticmethod
def noObjects():
print('No. of instances created: ', Myclass.n)
obj1 = Myclass()
obj2 = Myclass() OUTPUT
obj3 = Myclass() No. of instances created: 3
Myclass.noObjects()
In the next program, we accept a number from the keyboard and return the
result of its square root value. Here, there is no need of class or object and hence
we can write a static method to perform this task.
#a static method to find square root value
import math
class Sample: OUTPUT
@staticmethod Enter a number:4
def calculate(x): The square root of 4.0 is 2.00
result = math.sqrt(x)
return result
num = float(input('Enter a number:'))
res = Sample.calculate(num)
print('The square root of {} is {:.2f}'.format(num, res))
Passing Members of One Class to Another Class
It is possible to pass the members (i.e. attributes and methods) of a class to
another class. Let's create an instance of Emp class as:e = Emp()
Then pass this instance 'e' to a method of other class, as:
Myclass.mymethod(e)
Here, Myclass is the other class and mymethod() is a static method that belongs
to Myclass. In Myclass, the method mymethod() will be declared as a static
method as it acts neither on the class variables nor instance variables of
Myclass. The purpose of mymethod() is to change the attribute of Emp class
#class contains employee details
class Emp:
#constructor
def __init__(self,id,name,salary):
self.id=id
self.name=name
self.salary=salary
#instance method
def display(self):
print("ID= ",self.id)
print("Name= ",self.name)
print("Salary= ",self.salary)
Inner Classes
Writing a class within another class is called creating an inner class or nested
class. For example, if we write class B inside class A, then B is called inner
class or nested class. Inner classes are useful when we want to sub group the
data of a class. For example, let's take a person's data like name, age, date of
birth etc. Here, name contains a single value like ‘Abcd', age contains a single
value like ‘21' but the date of birth does not contain a single value. DoB
contains three values like date, month and year. So, we need to take these three
values as a sub group. Hence it is better to write date of birth as a separate class
Dob inside the Person class. This Dob will contain instance variables dd, mm
and yy which represent the date of birth details of the person.
#A python program to create Dob class within Person class
class Person:
def __init__(self):
self.name="Charles"
self.dob=self.Dob()
def display(self):
print("Name= ",self.name)
#inner class
OUTPUT
class Dob: Name= Charles
def __init__(self): DOB= 10/5/1988
self.dd=10
self.mm=5
self.yy=1988
def display(self):
print("DOB=
{}/{}/{}".format(self.dd,self.mm,self.yy))
Programmer 2
central database
Now another programmer in the same team wants to create Student class. He is planning the
Student class without considering the Teacher class as shown below:
#Student class
class Student:
def setid(self,id):
self.id=id
def getid(self):
return self.id
def setname(self,name):
self.name=name
def getname(self):
return self.name
def setaddress(self,address):
self.address=address
def getaddress(self):
returnself.address
def setmarks(self,marks):
self.marks=marks
def getmarks(self):
return self.marks
Now, the second programmer who created this Student class and saved it as student.py can
use it whenever he needs
#using Student class. Save this as inh.py
from student import Student
s=Student()
s.setid(100) OUTPUT
s.setname("Rakesh") ID= 100
s.setaddress("Bangalore")
Name= Rakesh
s.setmarks(970)
print("ID= ",s.getid()) Address= Bangalore
print("Name= ",s.getname()) Marks= 970
print("Address= ",s.getaddress())
print("Marks= ",s.getmarks())
If we compare the Teacher class and Student classes, we can understand that 75% of the code
is same in both the classes. Most of the code being planned by the second programmer in his
Student class is already available in the Teacher class. Then why doesn’t he use it for his
advantage? Our idea is this: instead of creating a new class altogether, he can reuse the code
which is already available. This is shown in below program:
#Python program to create Student class by deriving it from the
#Teacher class
from teacher import Teacher
class Student(Teacher):
def setmarks(self,marks):
self.marks=marks
def getmarks(self):
return self.marks
The preceding code will be same as the first version of the Student class. In the first statement
we are importing Teacher class from teacher module so that the Teacher class is now
available to this program. Then we are creating Student class as:
class Student(Teacher):
This means the Student class is derived from Teacher class. All the members of Teacher class
are available to the Student class. Hence we can use them without rewriting them in the
Student class. In addition, the following two methods are needed by the Student class but not
available in the Teacher class:
def setmarks(self,marks):
def getmarks(self):
In other words, we created Student class from the Teacher class. This is called Inheritance.
The original class i.e., Teacher class is called base class or super class and the newly created
class i.e., the Student class is called the Sub class or derived class. Deriving new classes
from the existing classes such that the new classes inherit all the members of the existing
classes is called Inheritance. The syntax for inheritance is:
class Subclass(Baseclass)
The advantages of inheritance are:
By inheritance, a programmer can develop the classes very easily. Hence
programmer’s productivity is increased. Productivity is a term refers to the code
developed by the programmer in a given span of time.
If the programmer uses inheritance, he will be able to develop more code in less time
More profits for the organization and better growth for the programmer.
In inheritance, we always create only the sub class object. Generally, we do not create super
class object. The reason is clear. Since all the members of the super class are available to sub
class, when we create an object, we can access the members of both the super and sub
classes. But if we create an object to super class, we can access only the super class members
and not the sub class members
Constructors in Inheritance
In previous programs, we have inherited the Student class from Teacher class. All the
methods and the variables in those methods of the Teacher class (base class) are accessible to
the Student class (sub class). Are the constructors of the base class accessible to the sub class
or not-is the next question we will answer. In the below program, we are taking a super class
by the name ‘Father’ and derived a sub class ‘Son’ from it. The Father class has a constructor
where a variable ‘property’ is declared and initialized with 800000.00. When Son is created
from Father, this constructor is by default available to Son class. When we call the method of
the super class using sub class object, it will display the value of ‘property’ variable.
#program to access the base class constructor from sub #class
class Father:
def __init__(self):
OUTPUT
self.property=800000.00
Father's property= 800000.0
def display_property(self):
print("Father's property= ",self.property)
class Son(Father):
pass #we dont want to write anything in the subclass
#create instance of sub class
s=Son()
s.display_property()
Overriding Super Class Constructors and Methods
When the programmer writes a constructor in the sub class, the super class constructor
is not available to the sub class. In this case, only the sub class constructor is accessible from
the sub class object. That means the sub class constructor is replacing the super class
constructor. This is called constructor overriding. Similarly in the sub class, if we write a
method with exactly same name as that of super class method, it will override the super class
method. This is called method overriding.
#constructor and method overriding
class Father:
def __init__(self):
self.property=800000
def display_property(self):
print("Father's property= ",self.property)
class Son(Father):
OUTPUT
def __init__(self):
Son's property= 200000.0
self.property=200000
def display_property(self):
print("Child’s property= ",self.property)
def display_property(self):
print("Father's property= ",self.property)
class Son(Father):
def __init__(self,property1=0,property=0):
super().__init__(property)
self.property1=property1 OUTPUT
Total property= 1000000.0
def display_property(self):
print("Total property= ", self.property1+ self.property)
Bank
AndhraBank StateBank
#single inheritance
class Bank(object):
cash=1000
@classmethod
def available_cash(cls):
print(cls.cash)
OUTPUT
class AndhraBank(Bank):
pass 1000
class StateBank(Bank): 3000
cash=2000
@classmethod
def available_cash(cls):
print(cls.cash+Bank.cash)
a=AndhraBank()
a.available_cash()
s=StateBank()
s.available_cash()
In the above program, we are deriving 2 sub classes AndhraBank and StateBank from the
single base class Bank. All the memebrs of Bank class will be vailable to the sub classes. In
the Bank class, we have a variable ‘cash’ and a method to display that is available_cash().
Here the variable ‘cash’ is declared in the class and initialized to 1000. The available_cash()
is a class method that is accessing the variable as ‘cls.cash’. When we derive AndhraBank
class from Bank class, the ‘cash’ variable and available_cash() method is accessible to
AndhraBank class. Similarly we can derive another subclass by the name StateBank from
Bank class as:
class StateBank(Bank):
cash=2000
@classmethod
def available_cash(cls):
print(cls.cash+Bank.cash)
Here StateBank has its own class variable ‘cash’ that contains 2000. So the total cash
available to StateBank is 1000+2000=3000. Observe the last line in the code:
print(cls.cash+Bank.cash)
Here, ‘cls.cash’ represents the current class’s cash variable and ‘Bank.cash’ represents the
Bank base class cash variable.
Multiple Inheritance
Deriving sub classes from multiple base classes is called ‘multiple inheritance’. In this type
of inheritance, there will be more than one super class and there may be one or more sub
classes. All the members of the super classes are by default available to sub classes and the
sub classes in turn can have their own members. The syntax is:
class Subclass(Baseclass1,Baseclass2,…):
Father Mother
Child
#multiple inheritance
class Father:
OUTPUT
def height(self):
Childs inherited qualities are:
print("Height is 6.0 foot")
Height is 6.0 foot
Color is brown
class Mother:
def color(self):
print("Color is brown")
c=Circle()
c.area(15)
Chapter 3
Abstract Classes and Interfaces
Abstract Method and Abstract Class
An abstract method is a method whose action is redefined in the sub classes as per the
requirement of the objects. Generally abstract methods are written without body since their
body will be defined in the sub classes. But it is possible to write an abstract method with
body also. To mark a method as abstract, we should use the decorator @abstractmethod. On
the other hand, a concrete method is a method with body.
An abstract class is a class that generally contains some abstract methods. Since,
abstract class contains abstract methods whose implementation or body is later defined in the
sub classes. It is not possible to estimate the total memory required to create the object for the
abstract class. So PVM can’t create objects to an abstract class.
Once an abstract class is written, we should create sub classes and all the abstract
methods should be implemented in the sub classes. Then it is possible to create objects to the
sub classes.
Let us create a class Myclass as an abstract super class with an abstract method
calculate (). This method does not have any body within it. The way to create an abstract
class is to derive it from a meta class ABC that belongs to abc (abstract base class) module
as:
class Abstractclass(ABC):
Since all abstract classes should be derived from the meta class ABC which belongs to abc
module, we should import this module into our program. A meta class is a class that defines
the behavior of other classes. The meta class ABC defines that the class which is derived
from it becomes an abstract class. To import abc module’s ABC class and abstract method
decorator we can write as follows:
from abc import ABC,abstractmethod
or
from abc import *
Now, our abstract class Myclass should be derived from ABC class as:
class Myclass(ABC):
@abstractmethod
def calculate(self,x):
pass
In the above code, Myclass is an abstract class since it is derived from ABC meta class. This
class has an abstract method calculate() that does not contain any code. We used
@abstractmethod decorator to specify that this is an abstract method. We have to write sub
classes where this abstract method is written with its body.
#abstarct class example
from abc import ABC,abstractmethod
class Myclass(ABC):
@abstractmethod
def calculate(self,x):
pass OUTPUT
#this is the sub class of Myclass Square value: 4
class Sub1(Myclass): Cube: 8
def calculate(self,x):
print("Square value: ",x*x)
#second sub class
class Sub2(Myclass):
def calculate(self,x):
print("Cube: ",x**3)
#Sub1 class object
obj1=Sub1()
obj1.calculate(2)
#Sub2 class object
obj2=Sub2()
obj2.calculate(2)
Interfaces in Python
An abstract class is a class which contains some abstract methods as well as concrete
methods also. Imagine a class that contains only abstract methods and there are no concrete
methods. It becomes an interface. Interface is an abstract class but it contains only abstract
methods. None of the methods in the interface will have body. Only method headers will be
written in the interface. So an interface can be defined as a specification of method headers.
Since, we write only abstract methods in the interface, there is possibility for providing
different implementations for those abstract methods depending on the requirements of
objects. In the languages like Java, an interface is created using the key ‘interface’ but in
Python an interface is created as an abstract class only. The interface concept is not explicitly
available in Python. We have to use abstract classes as interfaces in Python.
Since an interface contains methods without body, it is not possible to create objects
to an interface. In this case, we can create sub classes where we can implement all the
methods of the interface. Since the sub classes will have all the methods with body, it is
possible to create objects to the sub classes.
Consider the below class:
#this class works with Oracle database only
class Oracle:
def connect(self):
print("Connecting to Oracle database")
def disconnect(self):
print("Disconnected from Oracle")
This class has a limitation. It can connect only to Oracle database. If a client (user) using any
other database uses this code to connect to his database, this code will not work. So, the
programmer is asked to design his code in such a way that it is used to connect to any
database in the world.
One way is to write several classes, each to connect to a particular database. Thus,
considering all the databases available in the world, the programmer has to write a lot of
classes. This takes a lot of time and effort. Interface helps to solve this problem. The
programmer writes an interface ‘Myclass’ with abstract methods as shown here:
class Myclass(ABC):
@abstractmethod
def connect(self):
pass
@abstractmethod
def disconnect(self):
pass
We can’t create objects to the interface. So, we need classes where all these methods of the
interface are implemented to connect to various databases. Consider the sub class:
#subclass
class Oracle(Myclass):
def connect(self):
print("Connecting to Oracle database")
def disconnect(self):
print("Disconnected from Oracle")
#another subclass
class Sybase(Myclass):
def connect(self):
print("Connecting to Sybase database")
def disconnect(self):
print("Disconnected from Sybase")
Now, it is possible to create objects to the subclasses and call the connect() method and
disconnect() methods.
First, the programmer should accept the database name from the user. It may be
‘Oracle’ or ‘Sybase’. This name should be taken in a string say ‘str’. The next step is to
connect this string into a class name using the built in function globals(). The globals()
function returns a dictionary containing current global names and global()[str] returns the
name of the class that is in ‘str’. Hence we can get the class name
as:classname=globals()[str]
Now, create an object to this class and call the methods as:
x=classname()
x.connect()
x.disconnect()
The program is given below
#abstract class works like an interface
from abc import *
class Myclass(ABC):
@abstractmethod
def connect(self):
pass OUTPUT
@abstractmethod Enter database nameOracle
def disconnect(self): Connecting to Oracle database
pass Disconnected from Oracle
#subclass Enter database nameSybase
class Oracle(Myclass): Connecting to Sybase database
def connect(self): Disconnected from Sybase
print("Connecting to Oracle database")
def disconnect(self):
print("Disconnected from Oracle")
#another subclass
class Sybase(Myclass):
def connect(self):
print("Connecting to Sybase database")
def disconnect(self):
print("Disconnected from Sybase")
class Database:
#accept database name as a string
str=input("Enter database name")
#convert string into class name
classname=globals()[str]
#create an object
x=classname()
#call methods
x.connect()
x.disconnect()
Abstract Classes vs. Interfaces
Python does not provide interface concept explicitly. It provides abstract classes
which can be used as either abstract classes or interfaces. It is the wish of the programmer to
decide when to use an abstract class and when to go for an interface.
There is a responsibility for the programmer to provide the sub classes whenever he
writes an abstract class. This means the same development team should provide the sub
classes for the abstract class. But if an interface is written, any third party vendor will take the
responsibility of providing sub classes.
In case of an interface, every time a method is called,PVM should search for the
method in the implementation classes which are installed elsewhere in the system and then
execute the method. This takes more time. But when an abstract class is written, since the
common methods are defined within the abstract class and the sub classes are generally in the
same place along with the software,PVM will not have that much overhead to execute a
method. Hence,interfaces are slow when compared to abstract classes.
Chapter 4
Exceptions
Exceptions
An exception is a run time error which can be handled by the programmer. That
means if the programmer can guess an error in the program and he can do something to
eliminate the harm caused by that error, then it is called an ‘exception’. If the programmer
can’t do anything in case of an error, then it is called an ‘error’ and not an exception.
All exceptions are represented as classes in Python. The exceptions which are already
available in Python are called ‘built-in’ exceptions. The base class for all built-in exceptions
is ‘BaseException’ class. From ‘BaseException’ class, the sub class ‘Exception’ is derived.
From ‘Exception’ class, the sub classes ‘StandardError’ and ‘Warning’ are derived.
All errors (or exceptions) are defined as sub classes of ‘StandardError’. An error
should be compulsorily handled otherwise the program will not execute. Similarly, all
warnings are derived as sub classes from ‘Warning’ class. A warning represents a caution and
even though it is not handled, the program will execute. So, warnings can be neglected but
errors cannot be neglected.
Just like the exceptions which are already available in Python language, a
programmer can also create his own exceptions called ‘user defined’ exceptions. When the
programmer wants to create his own exception class, he should derive his class from
‘Exception’ class and not from ‘BaseException’ class. The below figure shows the important
classes available in Exception hierarchy
BaseException
Exception
StandardError Warning
ArithmeticError DeprecationWarning
AssertionError RuntimeWarning
SyntaxError ImportWarning
TypeError
EOFError
RuntimeError
ImportError
NameError
BaseException
Exception Handling
The purpose of handling errors is to make the program robust. The word robust means
strong. A robust program does not terminate in the middle. Also, when there is an error in the
program, it will display an appropriate message to the user and continue execution. Designing
such programs is needed in any software development. For this purpose, the programmer
should handle the errors. When the errors can be handled, they are called exceptions.
To handle exceptions, the programmer should perform the following 3 steps
Step 1: The programmer should observe the statements in his program where there may be a
possibility of exceptions. Such statements should be written inside a ‘try’ block. A try block
looks like as follows
try:
statements
The greatness of try block is that even if some exceptions arise inside it, the program will not
be terminated. When PVM understands that there is an exception, it jumps into an ‘except’
block.
Step 2: The programmer should write the ‘except’ block where he should display the
exception details to the user. This helps the user to understand that there is some error in the
program. The programmer should also display a message regarding what can be done to
avoid this error. Except block looks like as follows
except exceptionname:
statements #these statements from handler
The statements written inside an ‘except’ block are called ‘handlers’ since they handle the
situation when the exception occurs.
Step 3: Lastly, the programmer should perform clean up actions like closing the files and
terminating any other processes which are running. The programmer should write this code in
the finally block. Finally block looks like as follows
finally:
statements
Performing the above 3 tasks is called ‘exception handling’.
#an exception handling example OUTPUT 1
try: Enter the first number2
a=int(input("Enter the first number")) Enter the second number3
b=int(input("Enter the second number")) Terminated
c=a/b
except ZeroDivisionError: OUTPUT 2
print("Division by zero happened") Enter the first number2
print("Please do not enter 0") Enter the second number0
finally: Division by zero happened
print("Terminated") Please do not enter 0
Terminated
From the preceding output, we can understand that the ‘finally’ block is executed and the file
is closed in both the cases i.e. when there is no exception and when the exception occurred.
The complete exception handling syntax is:
try:
statements
except Exception1:
handler1
except Exception2:
handler2
else:
statements
finally:
statements
The ‘try’ block contains the statements where there may be one or more exceptions. The
subsequent ‘except’ blocks handle these exceptions. When ‘Exception1’ occurs, ‘handler1’
statements are executed. When ‘Exception2’ occurs, ‘handler2’ statements are executed and
so forth. If no exception is raised, the statements inside the ‘else’ block are executed. Even if
the exception occurs or does not occur, the code inside ‘finally’ block is always executed.
Types of Exceptions
There are several exceptions available as part of Python language that are called built-
in exceptions. Similarly, the programmer can also create his own exceptions called user-
defined exceptions. Most of the exception class names end with the word ‘Error’.
Exception Class Name Description
Exception Represents any type of exception. All exceptions are sub classes of this class
ArithmeticError Represents the base class for arithmetic errors like OverflowError,
ZeroDivisionError, FloatingPointError
AssertionError Raised when an assert statement gives error
AttributeError Raised when an attribute reference or assignment fails
EOFError Raised when input() function reaches end of file condition without reading any
data
FloatingPointError Raised when a floating point operation fails
GeneratorExit Raised when generators close() method is called
IOError Raised when an input or output operation failed.
ImportError Raised when an import statement fails to find the module being imported
IndexError Raised when a sequence index is out of range
KeyError Raised when a mapping (dictionary) key is not found in the set of existing
keys
KeyboardInterrupt Raised when the user hits the interrupt key (Ctrl-C or Delete)
NameError Raised when an identifier is not found locally or globally
OverflowError Raised when the result of an arithmetic operation is too large to be represented
RuntimeError Raised when an error is detected that does not fall in any of the other
categories
SyntaxError Raised when the compiler encounters a syntax error
IndentationError Raised when indentation is not specified properly
ZeroDivisionError Raised when the denominator is zero in a division or modulus operation
The Except Block
The ‘except’ block is useful to catch an exception that is raised in the try block. When
there is an exception in the try block, then only the except block is executed. It is written in
various formats.
1. To catch the exception which is raised in the try block, we can write except block with the
Exceptionclass name as:
except Exceptionclass:
2. We can catch the exception as an object that contains some description about the
exception
except Exceptionclass as obj:
3. To catch multiple exceptions, we can write multiple catch blocks. The other way is to use
a single except block and write all the exceptions as a tuple inside parenthesis as:
except(Exceptionclass1,Exceptionclass2,……):
4. To catch any type of exception where we are not bothered about which type of exception
it is, we can write except block without mentioning any Exceptionclass name as:
except:
We can use try block without except block. When we want to use try block alone, we
need to follow it with a finally block. Since we are not using except block, it is not
possible to catch the exception
#try without except block
try: OUTPUT
x=int(input("Enter a number")) Enter a number 5
y=1/x We don't catch the exception
finally: The inverse is 0.2
print("We don't catch the exception")
print("The inverse is",y)
The assert Statement
The assert statement is useful to ensure that a given condition is True. If it is true, it raises
assertion error. The syntax is as follows: assert condition, message
If the condition is False, then the exception by the name AssertionError is raised along with
the ‘message’ written in the assert statement. If message is not given in the assert statement,
and the condition is False, and then also AssertionError is raised without message.
Program 1:
#handling AssertionError
try:
x=int(input("Enter a number between 5 and 10: "))
assert x>=5 and x<=10
OUTPUT
print("The number entered: ",x)
Enter a number between 5 and 10:
except AssertionError:
6
print("The condition is not fulfilled") The number entered: 6
OUTPUT
Program 2:
Enter a number between 5 and 10:
#handling AssertionError with a message
7
try:
The number entered: 7
x=int(input("Enter a number between 5 and 10:"))
assert x>=5 and x<=10, "Your input is not correct"
print("The number entered: ",x)
except AssertionError as obj:
print(obj)
User Defined Exceptions
Like the built in exceptions of Python, the programmer can also create his own exceptions
which are called ‘user defined exceptions’ or ‘custom exceptions’.
To create his own exceptions he has to follow the following steps:
1. Since all exceptions are classes, the programmer is supposed to create his own exception
as a class. Also, he should make his class as a subclass to the in-built ‘Exception’ class
class MyException(Exception):
def __init__(self,arg):
self.msg=arg
Here ‘MyException’ class is the subclass for ‘Exception’ class. This class has a constructor
where a variable ‘msg’ is defined. The ‘msg’ variable receives a message passed from outside
through ‘arg’.
2. The programmer can write his code; maybe it represents a group of statements or a
function. When the programmer suspects the possibility of exception, he should raise his
own exception using raise statement as: raise MyException("message")
Here, raise statement is raising MyException class object that contains the given ‘message’.
3. The programmer can insert the code inside a ‘try’ block and catch the exception using
‘except’ block as:
try:
code
except MyException as me:
print(me)
Here the object ‘me’ contains the message given in the raise statement. All these steps are
shown in below program
#creating own class as sub class to Exception class
class MyException(Exception):
def __init__(self,arg):
self.msg=arg
#write code where exception may arise
#to raise the exception, use raise statement
def check(dict):
for k,v in dict.items():
print("name={:15s} Balance={}".format(k,v))
if(v<2000.00):
raise MyException("Balance amount is less")
bank={"Raj":5000.00,"Vani":8900.50,"Ajay":1990.00,
"Naresh":3000.00}
try: OUTPUT
check(bank) name=Raj Balance=5000.0
except MyException as me: name=Vani Balance=8900.5
name=Ajay Balance=1990.0
print(me)
Balance amount is less
The method names can be critical(), error(), warning(), info() and debug(). For example:
logging.critical("System crash")
Now this error message is stored into the log file that is ‘mylog.txt’.
Example 1:
#understanding logging of error messages
import logging
#store messages into mylog.txt file
logging.basicConfig(filename='mylog.txt',level=logging.ERROR)
#these messages are stored into the file.
logging.error("There is an error in the program")
logging.critical("There is a problem in the design")
#these messages are not stored
logging.warning("The project is going slow")
logging.info("You are a junior programmer")
logging.debug("Line no. 10 contains syntax error")
When the above program is executed, we can see a file created by the name ‘mylog.txt’ in
our current directory. Open the file to see the following message:
ERROR:root:There is an error in the program
CRITICAL:root:There is a problem in the design
Example 2:
import logging
logging.basicConfig(filename='log.txt',level=logging.ERROR)
try:
a=int(input("Enter the first number:")) OUTPUT
b=int(input("Enter the second number:")) Enter the first number:10
c=a/b Enter the second number:20
The result of division: 0.5
except Exception as e:
>>>
logging.exception(e)
Enter the first number:10
else:
Enter the second number:0
print("The result of division: ",c)
>>>
Enter the first number:10
Enter the second number:ab
Please observe that the program is executed 3 times with different inputs. First time the
values supplied are 10 and 20. With these values the program executed well and there are no
exceptions. Second time the values supplied is 10 and 0. In this case there is possibility for
ZeroDivisionError. The message related to this exception will be stored into our log file. In
the third time execution, the values entered are 10 and ab. In this case there is possibility for
ValueError. The message of this exception will also be added to our log file. Now we can
open the log file and see the following messages:
ERROR:root:division by zero
Traceback (most recent call last):
File "C:/jecci/exception/16.py", line 6, in <module>
c=a/b
ZeroDivisionError: division by zero
ERROR:root:invalid literal for int() with base 10: 'ab'
Traceback (most recent call last):
File "C:/jecci/exception/16.py", line 5, in <module>
b=int(input("Enter the second number:"))
ValueError: invalid literal for int() with base 10: 'ab'
Chapter 5
Regular Expressions
A regular expression is a string that contains special symbols and characters to find
and extract the information needed by us from the given data. Regular expression helps us to
search information, match, find and split information as per our requirement. A regular
expression is also called simply regex.
Python provides ‘re’module that stands for regular expressions. This module contains
methods like compile(), search(), match(), findall(), split(), etc which are used in finding the
information in the available data. So when we write a regular expression we should import
module as: import re
Regular expressions are nothing but strings containing characters and special symbols. A
simple regular expression may look like this: reg=r‘m\w\w’
In the above line, the string is prefixed with ‘r’ to represent that it is a raw string. Generally
we write regular expressions as raw strings. When we write a normal string as:
str= “This is a normal\string”
Now, print(str) will display the preceding string in two lines as:
This is a normal
string
Thus the ‘\n’ character is interpreted as new line in the normal string by the Python
interpreter and hence the string is broken there and shown in the new line. In regular
expressions when ‘\n’ is used, it does not mean to throw the string into new line. For this
purpose, we should take this as a ‘raw’ string. This is done by prefixing ‘r’ before the string.
str=r ‘This is raw\nstring’
When we display this string using print(str). The output will be:
This is raw\nstring
So, the normal meaning of ‘\n’ is escaped and it is no more an escape character in the
preceding example. Since ‘\n’ is not an escape character, it is interpreted as a character with
different meaning in the regular expression by the Python interpreter. Similarly, the
characters like ‘\t’, ‘\w’, ‘\c’, etc should be interpreted as special characters in the regular
expressions and hence the expressions should be written as raw strings. If we don’t want to
write the regular expressions as raw strings, then the alternative is to use another backslash
before such characters. For example,
reg=r‘m\w\w’ #as raw string
reg=‘m\\w\\w’ #as normal string
Consider the regular expression:
reg=r‘m\w\w’
This expression is written in single quotes to represent that it is a string. The first character
‘m’ represents that the words starting with ‘m’ should be matched. The next character ‘\w’
represents any one character in A to Z, a to z, and 0 to 9. Since we used two ‘\w’ characters,
they represent any two characters after ‘m’. So, this regular expression represents words or
strings having three characters and with ‘m’ as first characters. The next two characters can
be any alphanumeric.
Now we developed our first regular expression. The next step is to compile this
expression using compile () method of ‘re’ module as:
prog=re.compile(r‘m\w\w’)
Now prog represents an object that contains the regular expression. The next step is to run
this expression on a string ‘str’ using search() method or match() methods as:
str='cat mat bat rat'
#expression will act
result=prog.search(str)
The result is stored in ‘result’ object and we can display it by calling the group() method on
the object as:
print(result.group())
This is how a regular expression is created and used.
Example 1:
import re
prog=re.compile(r'm\w\w') OUTPUT
str='cat mat bat rat' mat
result=prog.search(str)
print(result.group())
Example 2:
import re
prog=re.compile(r'm\w\w') OUTPUT
str='Operating system format' mat
result=prog.search(str)
print(result.group())
Instead of compiling the first regular expression and then running the next one, we can use a
single step to compile and run all the regular expression as:
result=re.search(r'm\w\w')
The above code is equivalent to:
prog=re.compile(r'm\w\w')
result=prog.search(str)
So, the general form of writing regular expressions is:
result=re.search(‘expression’,‘string’)
Example:
import re
str='man sun mop run' OUTPUT
result=re.search(r'm\w\w',str) man
if result:
print(result.group())
Observe the output of above program. The search () method searches for the strings
according to the regular expression and returns only the first string. This is the reason that
even though there are two strings ‘man’ and ‘mop’, it returned only the first one. This string
can be extracted using the group () method. In case, the search () method could not find any
strings matching the regular expression, and then it returns None. So, to display the result, we
can use either of the statements given here:
if result is not None:
print(result.group())
if result:
print(result.group())
Suppose, we want to get all the strings that match the pattern mentioned in the regular
expression, we should use findall () method instead of search () method. The findall ()
method returns all resultant strings into a list.
import re
str='man sun mop run' OUTPUT
['man', 'mop']
result=re.findall(r'm\w\w',str)
print(result)
There is another method by the name match() that returns the resultant string only if it is
found in the beginning of the string. The match() method will give None if the string is not in
the beginning.
import re
str='sun mop run man' OUTPUT
result=re.match(r'm\w\w',str) None
print(result)
There is another method by the name split() that splits the given string into pieces according
to the regular expression and returns the pieces as elements of a list. Suppose we write a
regular expression as: re.split(r‘\W+’,str)
Observe the regular expression ‘\W’. This is capital ‘W’ which is reverse to the small ‘w’.
‘w’ represents any one alphanumeric character, i.e. A-Z, a-z, 0-9. But ‘W’ represents any
character that is not alpha numeric. So, the work of this regular expression is to split the
string ‘str’ at the places where there is no alpha numeric character. The ‘+’ after W represents
to match 1 or more occurrences indicated by ‘w’. The result is that the string will be split into
pieces where 1 or more non alpha numeric characters are found.
import re
OUTPUT
str='This; is the: "Core" Python\'s book' ['This', 'is', 'the', 'Core', 'Python', 's', 'book']
result=re.split(r'\W+',str)
print(result)
Sometimes, regular expressions can also be used to find a string and then replace it with a
new string. For this purpose, we should use sub() method of ‘re’ module. The format of this
method is: sub(regular expression,newstring,string)
Example:
import re
str='Kumbhamela will be conducted at Ahmedabad in India'
result=re.sub(r'Ahmedabad','Allahabad',str)
print(result)
OUTPUT
Kumbhamela will be conducted at Allahabad in India
So, regular expressions are used o perform the following important operations:
1. Matching strings
2. Searching for strings
3. Finding all strings
4. Splitting a string into pieces
5. Replacing strings
Sequence characters in Regular Expressions
Sequence characters match only one character in the string.
Character Description
\d Represents any digit (0 to 9)
\D Represents any non digit
\s Represents white space. Example: \t\n\r
\S Represents non-white space characters
\w Represents any alphanumeric
\W Represents non-alphanumeric
\b Represents a space around words
\A Matches only at start of the string
\Z Matches only at end of the string
Each of these sequence characters represents a single character matched in the string. For
example ‘\w’ indicates any one alphanumeric character. Suppose we write it as [\w]*. Here *
represents 0 or more repetitions.
import re
str='an apple a day keeps the doctor away' OUTPUT
result=re.findall(r'a[\w]*',str) ['an', 'apple', 'a', 'ay', 'away']
print(result)
The above output contains ‘ay’ which is not a word. This ‘ay’ is part of the word ‘away’. So
it is displaying both ‘ay’ and ‘away’ as they are starting with ‘a’. We do not want like this.
We want only the words starting with ‘a’. Since a word will have a space in the beginning or
ending, we can use \b before and after the words in the regular expression.
import re
OUTPUT
str='an apple a day keeps the doctor away'
['an', 'apple', 'a', 'away']
result=re.findall(r'\ba[\w]*\b',str)
print(result)
To retrieve all the words starting with a numeric digit like 0,1,2 or 9, \d is used.
import re
str='The meeting will be conducted on 1st and 21st of every month'
result=re.findall(r'\d[\w]*',str) OUTPUT
print (result) ['1st', '21st']
Character Description
\ Escape special character nature
. Matches any character except new line
^ Matches beginning of a string
$ Matches ending of a string
[…] Denotes a set of possible characters
[^…] Matches every character except the ones inside brackets
(…) Matches the regular expression inside the parenthesis and the result can be
captured
R|S Matches either regex R or regex S
To create a regular expression to search whether a given string is starting with ‘He’ or not:
import re
str="Hello World"
result=re.search(r"^He",str)
if result: OUTPUT
print("String starts with He") String starts with He
else:
print("String does not start with He")
To know, whether a string is ending with a word, we can use $ symbol.
import re
str="Hello World"
result=re.search(r"World$",str)
if result: OUTPUT
print("String ends with World") String ends with World
else:
print("String does not end with World")
If we use the expression with a small ‘w’ as r“world$”, then we will end up with wrong
output:
import re
str="Hello World"
result=re.search(r"world$",str)
OUTPUT
if result:
String does not end with world
print("String ends with world")
else:
print("String does not end with world")
We can specify a case insensitive matching of strings with the help of IGNORECASE
constant of ‘re’ module
import re
str="Hello World"
result=re.search(r"world$",str,re.IGNORECASE)
if result:
print("String ends with world") OUTPUT
else: String ends with world
print("String does not end with world")
The square brackets [] in regular expressions represent a set of characters. For example, if we
write [ABC], it represents any one character A or B or C. A hyphen (-) represents a range of
characters. For example, [A-Z] represents any single character from the range of capital
letters A to Z. the regular expression ‘[A-Z][a-z]*’ means the first letter should be any capital
letter. Then the next letter should be a small letter. The ‘*’ symbol represents 0 or more
repetitions of small letters.
import re
str="Rahul got 75 marks, Vijay got 55 marks, whereas Subbu got 98
marks"
marks=re.findall('\d{2}',str) OUTPUT
['75', '55', '98']
print(marks)
['Rahul', 'Vijay', 'Subbu']
names=re.findall('[A-Z][a-z]*',str)
print(names)
The pipe symbol (|) represents ‘or’. For example if we write am | pm, it finds the strings
which are either ‘am’ or ‘pm’
import re
str="The meeting may be at 8am or 9am or 4pm or 5pm"
result=re.findall(r'\dam|\dpm',str) OUTPUT
print(result) ['8am', '9am', '4pm', '5pm']
Chapter 6
Threads
Creating Threads in Python
Python provides ‘Thread’ class of threading module that is useful to
create threads. To create our own thread, we are supposed to create an object of
Thread class. The following are the different ways of creating our own threads
in Python:
1. Creating a thread without using a class
2. Creating a thread by creating a sub class to Thread class
3. Creating a thread without creating sub class to Thread class
Creating a thread without using a class
The purpose of a thread is to execute a group of statements like a
function. So, we can create a thread by creating an object of Thread class and
pass the function name as target for the thread as:
t=Thread(target=functionname, [args=(arg1,arg2,….)])
Here, we are creating the Thread class object ‘t’ that represents our
thread. The ‘target’ represents the function on which the thread will act. ‘args’
represents a tuple of arguments which are passed to the function. Once the
thread is created like this, it should be started by calling the start () method as:
t.start()
Then the thread ‘t’ will jump into the target function and executes the code
inside that function.
from threading import * OUTPUT
def display(): Hello I am running
print("Hello I am running") Hello I am running
Hello I am running
for i in range(5):
Hello I am running
t=Thread(target=display) Hello I am running
t.start()
We can slightly improve the previous program such that we can pass arguments
or data to the display () function.
OUTPUT
from threading import * Hello
def display(str): Hello
print(str) Hello
Hello
for i in range(5):
Hello
t=Thread(target=display,args=("Hello",))
t.start()
Note the comma (,) after the argument ‘Hello’ mentioned in the tuple. We
should remember that when a single element is specified in a tuple, a comma is
needed after that element
Creating a Thread by creating a subclass to Thread Class
Since ‘Thread’ class is already created by the Python people in the threading
module, we can make our class as a sub class to ‘Thread’ class so that we can
inherit the functionality of the Thread class. This can be done by writing the
following statement:
class MyThread(Thread)
Here ‘MyThread’ represents the sub class of ‘Thread’ class. Now, any
methods of Thread class are also available to our sub class. The ‘Thread’ class
has the run () method which is also available to our sub class i.e. to ‘MyThread’
class. The specialty of the run() method is that every thread will run this method
when it is started. So, by overriding the run () method, we can make the threads
run our own run() method. Overriding a method – means writing our own
method with the same name as the run() method of the super class.
The next step is to create the object of the ‘MyThread’ class which
contains a copy of the super class, i.e. the ‘Thread’ class.
t1=MyThread
Here, t1 is the object of ‘MyThread’ class which represents our thread. Now, we
can run this thread by calling the start() method as: t1.start(). Now the
thread will jump into the run() method of ‘MyThread’ class and executes the
code available inside it.
Many times it is better to wait for the completion of the thread by calling the
join() method on the thread as:t1.join(). This will wait till the thread completely
executes the run() method. A thread will terminate automatically when it comes
out of the run() method.
Exmaple:
from threading import Thread OUTPUT
class MyThread(Thread): Hello
def __init__(self,str):
Thread.__init__(self)
self.str=str
def run(self):
print(self.str)
t1=MyThread("Hello")
t1.start()
t1.join()
We can create an independent class say ‘MyThread’ that does not inherit from
‘Thread’ class. Then we can create an object ‘obj’ to ‘MyThread’ class as:
obj=MyThread(“Hello”)
The next step is to create a thread by creating an object to ‘Thread’ class and
specifying the method of the ‘MyThread’ class as its target as:
t1=Thread(target=obj.display,args=(1,2))
Here ‘t1’ is our thread which is created as an object of ‘Thread’ class. ‘target’
represents the display () method of ‘MyThread’ object ‘obj’. ‘args’ represents a
tuple of values passed to the method. When the thread ‘t1’ is started, it will
execute the display() method of ‘MyThread’ class.
Method Description
t.start() Starts the thread. It arranges for the object’s run() method to be
invoked in a separate thread of control
t.join([timeout]) Waits until the thread terminates or timeout occurs. ‘timeout’ is a
floating point number specifying a timeout for the operation in
seconds (or fraction of seconds)
t.is_alive() Returns True if the thread is alive in memory and False otherwise.
A thread is alive from the moment the start() method returns until
its run() method terminates
t.setName(name) Gives a name to the thread
t.getName() Returns name of the thread
t.name This is a property that represents the thread’s name
t.setDaemon(flag) Makes a thread a daemon thread if the flag is True
t.isDaemon() Returns True if the thread is a daemon thread, otherwise False
t.daemon This is a property that takes either True or False to set the thread as
daemon or not
Thread Synchronization
1. Using locks
2. Using semaphores
Locks
Locks can be used to lock the object on which the thread is acting. When
a thread enters the object, it locks the object and after the execution is
completed, it will unlock the object and comes out of it. It is a like a room with
only one door. A person has entered the room and locked it from behind. The
second person who wants to enter the room should wait till the first person
comes out. In this way, a thread also locks the object after entering it. Then the
next thread cannot enter it till the first thread comes out, this means the object is
locked mutually on threads. So, this locked object is called ‘mutex’
t2
t1
wait till t1 comes out
Example:
#thread synchronization using locks
from threading import *
from time import *
class Railway:
#constructor
def __init__(self,available):
self.available=available
#create a lock object
self.l=Lock()
# method
def reserve(self,wanted):
#lock the current object
self.l.acquire()
#display no.of available seats
print("Available number of seats= ",self.available)
if(self.available>=wanted):
#find the thread name
name=current_thread().getName()
print("%d seat alloted for %s"%(wanted,name))
#decrease the no.of available seats
self.available-=wanted
else:
print("sorry, no seats available")
self.l.release()
#create instance
obj=Railway(1)
#create two threads
t1=Thread(target=obj.reserve,args=(1,))
t2=Thread(target=obj.reserve,args=(1,))
#give names to the threads
t1.setName('First Person')
t2.setName('Second Person') OUTPUT
#run the threads Available number of seats= 1
1 seat alloted for First Person
t1.start()
Available number of seats= 0
t2.start()
sorry, no seats available
Semaphore
If the ‘countervalue’ is not given, the default value of the counter will be
1. When the acquire () method is called, the counter gets decremented by 1 and
when release () method is called, it is incremented by 1. These methods are used
in the following format:
#thread communication
from threading import *
from time import *
#create Producer class
class Producer:
def __init__(self):
self.lst=[]
self.dataprodover=False
def produce(self):
#create 1 to 10 items and add to the list
for i in range(1,11): #repeat from 1 to 10
self.lst.append(i) #add item to list
sleep(1) #every item may take some time
print("Item Produced")
#inform the onsumer that the production is completed
self.dataprodover=True
#Consumer class
class Consumer:
def __init__(self,prod):
self.prod=prod
def consume(self):
#sleep for 100 ms as long as dataprodover is False
#prod represents Producer instance that contains
dataprodover
whileself.prod.dataprodover==False:
OUTPUT
sleep(0.1)
Item Produced
#display the contents
Item Produced
print(self.prod.lst)
Item Produced
#create Producer object
Item Produced
p=Producer() Item Produced
#create Consumer object Item Produced
c=Consumer(p) Item Produced
#create threads Item Produced
t1=Thread(target=p.produce) Item Produced
t2=Thread(target=c.consume) Item Produced
#run threads [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
t1.start()
t2.start()
Daemon Threads
Sometimes, we need threads to run continuously in memory. For example, an
Internet server that runs continuously and caters to the needs of the client.
Garbage collector runs continuously and deletes the unused variables and
objects while a Python program is being executed. Such threads which run
continuously are called ‘daemon’ threads. Generally daemon threads are used to
perform some background tasks. For example, a daemon threads may send data
to the printer in the background while the user is working with other
applications. To make a threads a daemon thread, we can use the
setDaemon(True) method or assign True to daemon property.
In below program, we are creating a function display() that displays a
number once in a second, starting from 1 to 5. This function is executed by
normal thread‘t’. Then we create another function display_time() that displays
system date and time once in every 2 seconds. This function is executed by
another thread‘d’ which we make daemon by writing: d.daemon=True. When
both the threads are started, the results are shown in the output at the end of the
program.
from threading import *
from time import *
def display(): OUTPUT
for i in range(5): Normal thread: 1
print("Normal thread: ",i+1) Daemon thread: Wed Feb 23 15:34:48 2022
sleep(1) Normal thread: 2
def display_time(): Daemon thread: Wed Feb 23 15:34:50 2022
while True: Normal thread: 3
print("Daemon thread: ",ctime()) Normal thread: 4
sleep(2) Daemon thread: Wed Feb 23 15:34:52 2022
t=Thread(target=display)
Normal thread: 5
t.start()
d=Thread(target=display_time)
d.daemon=True
d.start()
The above program terminates when the normal thread ‘t’ after displaying the
number 5 is terminated. The reason is that when the normal threads are
terminated, the main thread will also terminate and thus the program is also
terminated. So, the daemon thread is also terminated in this program. To make
the daemon thread not to terminate, we have to ask the PVM to wait till the
daemon thread completely executes. This is done by using the join() method as:
d.join()