Download as pdf or txt
Download as pdf or txt
You are on page 1of 26

UNIT – V

CHAPTER-1 Inheritance

SYLLABUS: Inheritance: introduction, inheriting classes in python, types of inheritance,


composition or containership or complex objects, abstract classes and interfaces,
metaclasses.

The inheritance is a very useful and powerful concept of object-oriented programming.


Using the inheritance concept, we can use the existing features of one class in another class.
The inheritance is the process of acquiring the properties of one class to another class.

The technique of creating a new class from an existing class is called inheritance. The old or
existing class is called the base class and the new class is known as the derived class or sub-
class. The derived classes are created by first inheriting the data and methods of the base class
and then adding new specialized data and functions in it. In this process of inheritance, the base
class remains unchanged.
The concept of inheritance is used to implement the is-a relationship.
For example, teacher IS-A person, student IS-A person; while both teacher and student are a
person in the first place, both also have some distinguishing features. So all the common traits of
teacher and student are specified in the Person class and specialized features are incorporate in
two separate classes- Teacher and Student.
Inheritance which follows a top down approach to problem solving. In top-down approach,
generalized classes are designed first and then specialized classes are derived by
inheriting/extending the generalized classes.

Inheriting Classes in Python

In Python, we use the following general structure to create a child class from a parent class.

Syntax:

class DerivedClass(BaseClass): body_of_derived_class


Polymorphism and Method Overriding

Polymorphism refers to having several different forms. It is one of the key features of OOP. It
enables the programmers to assign a different meaning or usage to a variable, function, or an
object in different contexts.

While inheritance is related to classes and their hierarchy, polymorphism, on the other hand, is
related to methods. When polymorphism is applied to a function or method depending on the
given parameters, a particular form of the function can be selected for execution. In Python,
method overriding is one way of implementing polymorphism.
Types of Inheritance

Python support the six different types of inheritance as given below :


1. Single inheritance
2. Multi-level inheritance
3. Multiple inheritance
4. Multipath inheritance
5. Hierarchical Inheritance
6. Hybrid Inheritance

Single Inheritance
When a child class inherits only a single parent class.
class Parent:
def func1(self):
print("this is function one")
class Child(Parent):
def func2(self):
print(" this is function 2 ")
ob = Child()
ob.func1()
ob.func2()

Multiple Inheritance
When a child class inherits from more than one parent class.
class Parent:
def func1(self):
print("this is function 1")
class Parent2:
def func2(self):
print("this is function 2")
class Child(Parent , Parent2):
def func3(self):
print("this is function 3")

ob = Child()
ob.func1()
ob.func2()
ob.func3()

Multilevel Inheritance
When a child class becomes a parent class for another child class.
class Parent:
def func1(self):
print("this is function 1")
class Child(Parent):
def func2(self):
print("this is function 2")
class Child2(Child):
def func3("this is function 3")
ob = Child2()
ob.func1()
ob.func2()
ob.func3()

Method Resolution Order (MRO)


Method Resolution Order (MRO) is an approach that a programming language takes to resolve
the variables or methods of a class.

Python has a built-in base class named as the object. So, any other in-built or user-defined class
which you define will eventually inherit from it.

Now, let’s talk about how the method resolution order (MRO) takes place in Python.

In the multiple inheritance use case, the attribute is first looked up in the current class. If it fails,
then the next place to search is in the parent class, and so on.
If there are multiple parent classes, then the preference order is depth-first followed by a left-
right path, i.e., DLR.
MRO ensures that a class always precedes its parents and for multiple parents, keeps the order as
the tuple of base classes.

Hierarchical Inheritance
Hierarchical inheritance involves multiple inheritance from the same base or parent class.
class Parent:
def func1(self):
print("this is function 1")
class Child(Parent):
def func2(self):
print("this is function 2")
class Child2(Parent):
def func3(self):
print("this is function 3")

ob = Child()
ob1 = Child2()
ob.func1()
ob.func2()
Hybrid Inheritance
Hybrid inheritance involves multiple inheritance taking place in a single program.
class Parent:
def func1(self):
print("this is function one")

class Child(Parent):
def func2(self):
print("this is function 2")

class Child1(Parent):
def func3(self):
print(" this is function 3"):

class Child3(Parent , Child1):


def func4(self):
print(" this is function 4")

ob = Child3()
ob.func1()

Multi-path Inheritance
Deriving a class from other derived classes that are in turn derived from the same base
class is called multi-path inheritance.
What is Diamond problem in inheritance in Python?
The “diamond problem” (sometimes referred as the “deadly diamond of death”) is the generally
used term for an ambiguity that arises when two classes B and C inherit from a superclass A, and
another class D inherits from both B and C.

The Solution of Diamond Problem

The solution to the diamond problem is default methods and interfaces. … The advantage of
interfaces is that it can have the same default methods with the same name and signature in two
different interfaces. It allows us to implement these two interfaces, from a class.

Composition or Containership or Complex Objects

Complex objects are objects that are built from smaller or simpler objects. For example, a car is
built using a metal frame, an engine, some tyres, a transmission, a steering wheel, and several
other parts. Similarly, a computer system is made up of several parts such as CPU, motherboard,
memory, and so on. This process of building complex objects from simpler ones is called
composition or containership.
In object-oriented programming languages, object composition is used for objects that have a
has-a relationship to each other. For example, a car has-a metal frame, has-an engine, etc. A
personal computer has-a CPU, a motherboard, and other components.
Until now, we have been using classes that have data members of built-in type. While this
worked well for simple classes, for designing classes that simulate real world applications,
programmers often need data members that belong to other simpler classes.

The advantages of using this concept are:

 Each individual class can be simple and straightforward.


 One class can focus on performing one specific task and obtain one behavior.
 The class is easier to write, debug, understand, and usable by other programmers.
 While simpler classes can perform all the operations, the complex class can be designed
to coordinate the data flow between the simpler classes.
 It lowers the overall complexity of the complex object because the main, task of the
complex object would then be to delegate tasks to the sub-objects, who already know
how to do them.

Scope of Use:
Although there are no well-defined rules to state when a programmer must use the composition,
as a rule of thumb, each class should be built to accomplish a single task. The task should be to
either perform some part of manipulation or be responsible for coordinating other classes but
cannot perform both tasks. This immensely increases the maintenance of the code and future
updations because, when we wish to update a feature or an object, only the class pertaining to
that specific functionality needs to be updated. And also, it’s very easy to keep track of the errors
in the program.
Example:
Inheritance vs Composition

Abstract Classes and Interfaces

It is possible to create a class which cannot be instantiated. This means that that you cannot
create objects of that class. Such classes could only be inherited and then an object of the derived
class was used to access the features of the base class. Such a class was known as the abstract
class.
An abstract class corresponds to an abstract concept. For example, a polygon may refer to a
rectangle, triangle or any other closed figure. Therefore, an abstract class is a class that is
specifically defined to lay a foundation for other classes that exhibits a common behavior or
similar characteristics. It is primarily used only as a base class for inheritance.
Since an abstract class, is an incomplete class, users are not allowed to create its object. To use
such a class, programmers must derive it and override the features specified in that class.

An abstract class just serves as a template for other classes by defining a list of methods that the
classes must implement. In Python, we use the NotImplementedError to restrict the instantiation
of a class. Any class that has the NotImplementedError inside method definitions cannot be
instantiated.

Metaclass
A metaclass is the class of a class. While a class defines how an instance of the class behaves, a
metaclass, on the other hand, defines how a class behaves. Every class that we create in Python,
is an instance of a metaclass.
For example, type is a metaclass in Python. It is itself a class, and it is its own type. Although,
you cannot make an exact replica of something like type, but Python does allow you to create a
metaclass by making a subclass type.
A metaclass is most commonly used as a class-factory. It allows programmers to create an
instance of the class by calling the class, Python allows you to define normal methods on the
metaclass which are like classmethods, as they can be called on the class without an instance.
You can even define the normal magic methods, such as __add__, __iter__ and __getattr__, to
implement or change how the class behaves.
Error and Exception handling
Introduction to errors and exception
Sometimes while executing a Python program, the program does not execute at all or the
program executes but generates unexpected output or behaves abnormally. These occur when
there are syntax errors, runtime errors or logical errors in the code. In Python, exceptions are
errors that get triggered automatically. However, exceptions can be forcefully triggered and
handled through program code.

Syntax Errors
Syntax errors are detected when we have not followed the rules of the particular programming
language while writing a program. These errors are also known as parsing errors. On
encountering a syntax error, the interpreter does not execute the program unless we rectify the
errors, save and rerun the program. When a syntax error is encountered while working in shell
mode, Python displays the name of the error and a small description about the error as shown in
Figure

So, a syntax error is reported by the Python interpreter giving a brief explanation about the error
and a suggestion to rectify it.
Logical errors
Logical errors are the most difficult to fix. They occur when the program runs without crashing,
but produces an incorrect result. The error is caused by a mistake in the program’s logic. You
won’t get an error message, because no syntax or runtime error has occurred. You will have to
find the problem on your own by reviewing all the relevant parts of your code – although some
tools can flag suspicious code which looks like it could cause unexpected behaviour.
Sometimes there can be absolutely nothing wrong with your Python implementation of an
algorithm – the algorithm itself can be incorrect. However, more frequently these kinds of errors
are caused by programmer carelessness. Here are some examples of mistakes which lead to
logical errors:
• using the wrong variable name
• indenting a block to the wrong level
• using integer division instead of floating point division
• getting operator precedence wrong
• making a mistake in a boolean expression
• off-by-one, and other numerical errors
Exceptions
Even if a statement or expression is syntactically correct, there might arise an error
during its execution. For example, trying to open a file that does not exist, division by zero and
so on. Such types of errors might disrupt the normal execution of the program and are called
exceptions. An exception is a Python object that represents an error. When an error occurs during
the execution of a program, an exception is said to have been raised. Such an exception needs to
be handled by the programmer so that the program does not terminate abnormally. Therefore,
while designing a program, a programmer may anticipate such erroneous situations that may
arise during its execution and can address them by including appropriate code to handle that
exception. Most exceptions are not handled by programs, however, and result in error messages
as shown here:
>>> 10 * (1/0)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
>>> 4 + spam*3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'spam' is not defined
>>> '2' + 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only concatenate str (not "int") to str
The last line of the error message indicates what happened. Exceptions come in different types,
and the type is printed as part of the message: the types in the example
are ZeroDivisionError, NameError and TypeError. The string printed as the exception type is the
name of the built-in exception that occurred. This is true for all built-in exceptions, but need not
be true for user-defined exceptions (although it is a useful convention). Standard exception
names are built-in identifiers (not reserved keywords).
Handling Exceptions

Each and every exception has to be handled by the programmer to avoid the program
from crashing abruptly. This is done by writing additional code in a program to give proper
messages or instructions to the user on encountering an exception. This process is known as
exception handling.
An exception is said to be caught when a code that is designed to handle a particular
exception is executed. Exceptions, if any, are caught in the try block and handled in the except
block. While writing or debugging a program, a user might doubt an exception to occur in a
particular part of the code. Such suspicious lines of codes are put inside a try block. Every try
block is followed by an except block. The appropriate code to handle each of the possible
exceptions (in the code inside the try block) are written inside the except clause.
...
try:
<--program code-->
except:
<--exception handling code-->
<--program code-->
...
The try statement work as follows,

Step 1 - First the try block (statements between the try and except keywords) is executed.

Step 2a - If no exception occurs, during execution of any statement in the try block then,

(2b) - Rest of the statement in try block are skipped.


(2c) - If the type of exception matches the exception named after the except keyword , the except
block is executed and execution
continues after the try statement.
(2d) - If an exception occurs which does not matches the exception name in the except block,
then it is passed on to the outer block...

EXAMPLE :

1 - PROGRAM TO HANDLE THE DIVIDE BY ZERO EXCEPTION ?

num = int(input("Enter the numerator : "))

deno = int(input("Enter the denominator : "))

try:
quo = num/deno
print("QUOTIENT:" , quo)

except ZeroDivisionError:

print("Denominator cannot be zero")

OUTPUT

Enter the numerator : 20

Enter the denominator : 0

Denominator cannot be zero

Multiple Except Blocks

Python allows you to have multiple except blocks for a single try block. The block which
matches with the exception generated will get executed. A try block can be associated with more
then one except block to specify handlers for different exceptions. However only one handler
will be executed.. Exception handlers specify handlers for different exception that occurs in
corresponding try block. We can write our program that handle selected exceptions. The syntax
for specifying multiple except blocks for a single try block can be given as,

try:

operation are done in this block

..................................................

except Exception1:

if there is Exception1, then execute this block.

except Exception2:

if there is Exception2, then execute this block.

...................................................
else:

if there is no exception execute this block.

..................................................

we will read about the else block which is optional a little later. But for now we have seen that
single try can have multiple except statements to catch different type of exceptions. For example
, look at the code given below. The program prompt user to enter a number. It then squares the
number and print its result. However if we do not specify any number or enter a non integer ,
then an exception will be generated. We have two except blocks. The one matching the case will
finally execute. This is very much evident from the output.

EXAMPLE :

Program with multiple except block ?

try:

num=int(input("Enter the numbers : ")

print(num**2)

except(KeyboardInterrupt):

print("You should have entered a number... Program terminating...")

except(ValueError):

print("Please check before entering..... Program terminating...")

print("Bye")

Output

Enter the numbers : abc

Please check before entering..... Program terminating...

Bye
Note :

1- After execution of except block , program control goes to the first statement after the except
block for that try block..

2 - The except block without an exception can also be used to print an error message and the re-
raise the exception..

Multiple Exceptions In Single Block

An except clause may name multiple exception as a parenthesized tuple , as shown in the given
program below. So whatever exception is raised , out of three exceptions specified, the same
except block will be executed.

Example :-

Program having an except clause handling multiple exception simultaneously.

try:

num=int(input("Enter the number : ")

print(num**2)

except(KeyBoardInterrupt, ValueError , TypeError):

print("Please Check before you enter ... Program Terminating..")

print("Bye")

Output

Enter the number : abc

Please Check before you enter ... Program Terminating..

Bye
Thus we see that if we want to give a specific exception handler for any exception raised , we
can better have multiple except blocks. Otherwise, if we want the same code to be executed for
all three exceptions then we can use the except(list_of_exceptions) format.

Except Block Without Exception

You can even specify an except block without mentioning any exception(ie,.except:). This type
of except block if present should be the last one that can serve as a wildcard(when multiple
except block are present).But use it with extreme caution, since it may mask a real programming
error.

In large software programs, may a times, it is difficult to anticipate all type of possible
exceptional conditions. Therefore the programmer may not be able to write a different
handler(except block) for every individual type of exception. In such situations, a better idea is to
write a handler that would catch all type of exceptions. The syntax to define a handler that would
catch every possible exception from try block is,

try:

write operation here

.................................

except:

if there is any exception, then execute this block

else:

if there is no exception then execute this block.

The except block can be used along with other exception handlers which handle some specific
type of exceptions but those exceptions that are not handled by these specific handlers can be
handled by except block. However , the default handler must be placed after all other except
blocks because otherwise it would prevent any specific handler to be executed.

Program to demonstrate the use of except: block


try:

fil = pen("file1.txt")

str = f.readline()

print(str)

except IOError:

print("Error occured during input.. program terminating...")

except ValueError:

print("Could not convert data to an integer")

except:

print("Unexpected Error.... Program termination..")

Output

Unexpected Error.... Program termination..

Programing Tip!

When an exception occurs, it may have an associated value, aslo known as the exception's
argument..

Note!

Using except: without mentioning any specific exception is not a good programming practice
because it catches all the exceptions and does not make programmer identify the root cause of
the problem.

The Else Clause


The try.... except block optionally have an else clause , which when present , must follow all
except blocks. The statements in the block is executed only if try clause does not raise an
exception. For example, the codes given below illustrate both the cases. This will help you
visualize the relevance of else block.
Example
Programs to demonstrate else block
try:
file = pen("file1.txt")
str = f.readline()
print(str)
except IOError:
print("Error occured during input.. program terminating...")
else:
print("program terminating successfully")
Output
Hello
program terminating successfully
Output
Error occured ..... program terminating...
Raising An Exception...
You can deliberately raise an exception using the raise keyword. The general syntax for the raise
statement is ,
>>>raise[Exception[,args[,traceback]]]
Here , Exception is the name of exception to be raised (example type error) args are optional and
specifies a value for the exception argument. If args is not specified , then the exception
argument is none. The final arguments , traceback , is also optional and if present , is the trace
back object used for exception.
For example , the code given below simply create a variable and print its value. There is no error
in the code but we have deliberately raised an exception.
Example
Program to deliberately raise an exception
try:
num=10
print(num)
raise ValueError
except:
print("Exception Occured ... Program Terminating ..)
output
Exception Occured ... Program Terminating ..
The only argument to raise keyword specifies the exception to be raised . Recall that, we had
earlier said that you can re -raise an exception from the except : block.
Program to raise an exception
try:
raise NameError
except:
print("Re-raising the exception")
raise
output
Re-raising the exception
Traceback (most recent call last):
file "C:Python34\Try.py:", line 2, in <module>
raise NameError
NameError
Programming Tip!
Avoid using except block without any exception
Instantiating Exceptions
Python allows programmers to instantiate an exception first before raising it and add any
attributes (or arguments) to it as desired. Those attributes can be used to give additional
information about the errors. To instantiate the exception , the except block may specify a
variable after the exception name . The variable then becomes an exception instance with the
arguments stored in instance.args . The exception instance also has __str__() method defined so
that the arguments can be printed directly without using instance.args.
Note!
The content of the argument vary based on exception type.
Example
Program to understand the process of instantiating an exception
try:
raise Exception("Hello" , "World")
except Exception as errorObj:
print(type(errorObj)
print(errorObj.args)
print(errorObj)
arg1 , arg2 = errorObj.args
print('Argument1=' , arg1)
print('Argument2 =' , arg2)
output
<type 'exceptions.Exception'>
("Hello" , "World")
("Hello" , "World")
Argument1=Hello
Argument2 =World
Note!
If you raise an exception with arguments but do not handle it , then the name of the exception is
printed along with its aerguments..
Program to raise an exception with arguments
try:
raise Exception("hello" , "world")
except ValueError:
print("program terminating..")
output
Exception: ("hello" , "world")

Built-in Exceptions
Exception description
Exception Base class for all exceptions
StopIteration Raised when the next() method of an iterator does not point to any
object.
Raised by the sys.exit() function.
SystemExit
Base class for all built-in exceptions except StopIteration and
StandardError
SystemExit.
Base class for all errors that occur for numeric calculation.
ArithmeticError
Raised when a calculation exceeds maximum limit for a numeric
OverflowError
type.
Raised when a floating point calculation fails.
FloatingPointError
Raised when division or modulo by zero takes place for all numeric
ZeroDivisionError
types
Raised in case of failure of the Assert statement.
AssertionError
Raised in case of failure of attribute reference or assignment.
AttributeError
Raised when there is no input from either the raw_input() or input()
EOFError
function and the end of file is reached.
Raised when an import statement fails.
ImportError
Raised when the user interrupts program execution, usually by
KeyboardInterrupt
pressing Ctrl+c.
Base class for all lookup errors.
LookupError
Raised when an index is not found in a sequence.
IndexError
Raised when the specified key is not found in the dictionary.
KeyError
Raised when an identifier is not found in the local or global
NameError
namespace.

UnboundLocalError Raised when trying to access a local variable in a function or


method but no value has been assigned to it.

EnvironmentError Base class for all exceptions that occur outside the Python
environment.
IOError Raised when an input/ output operation fails, such as the print
statement or the open() function when trying to open a file that does
not exist.

IOError Raised for operating system-related errors.

SyntaxError Raised when there is an error in Python syntax.

IndentationError Raised when indentation is not specified properly.

SystemError Raised when the interpreter finds an internal problem, but when this
error is encountered the Python interpreter does not exit.

SystemExit Raised when Python interpreter is quit by using the sys.exit()


function. If not handled in the code, causes the interpreter to exit.

TypeError Raised when an operation or function is attempted that is invalid for


the specified data type.

ValueError Raised when the built-in function for a data type has the valid type
of arguments, but the arguments have invalid values specified.

RuntimeError Raised when a generated error does not fall into any category.

NotImplementedError Raised when an abstract method that needs to be implemented in an


inherited class is not actually implemented.

The Finally Block


The try block has another optional block called finally which is used to define clean-up actions
that must be executed under all circumstances. The Finally block is always executed before
leaving the try block. This means that the statement written in the finally block are executed
irrespective of whether an exception has occurred or not. The Syntax of finally block can be
given as,
try:
>>>write your operation here
.........................................
>>>Due to any exception, operation written here will bew skipped
finally:
>>>This would always be executed.
.......................................
let us see with the help of program how finally block will behave when an exception is raised in
try block and is not handled by except block.
Example
Program to with finally block that leave the exception unhandled.
try:
>>>print("Raising exception.....")
>>>raise ValueError
finally:
>>>print("Performing cleanup in finally...")
Output
Raising exception.....
Performing cleanup in finally...
Traceback (most recent call last):
File "<string>", line 3, in <module>
ValueError
From the above code We can conclude that when an exception occurs in the try block and is not
handled by an exception block or if the exception occur in the except or else block , then it is re-
raised after executing the finally block. The finally block is also executed when any block of the
try block is exited via break , continue or return statement.
Now let us see the flow of control in a program that has try, except as well as finally block in the
program given below,
Example
Program to illustrate the use of try except and finally block all together.
try:
>>>print("Raising exception.....")
>>>raise ValueError
except:
>>>print("Exception caught")
finally:
>>>print("Performing cleanup in finally...")
Output
Raising exception.....
Exception caught
Performing cleanup in finally...
From the output you can see that the finally block is executed when exception occurred and also
when exception does not occur.
In Real world applications, the finally Clause is useful for releasing external resources like file
handles , network connections , memory resources, etc. regardless of whether the use of the
resources was successful.
Note : You cannot have an else Block with a finally block
If you place the finally block immediately after the try block and followed by the execute block
(maybe in case of nested try block) , then if an exception is raised in the try block , the code
finally will be executed first. The Finally block will perform the operations written in it and re-
raise the exception. This exception will be handled by the except block if present in the next
higher layer of try-except.This is shown in the program given below,
Example
Program having finally block to reraise the exception that will be handled by an outer try-except
block.
try:
print("Divided Strings....")
try:
quo = "abc"/"def"
finally:
print("in finally block...")
except TypeError:
print("In except block... handling Type Error...")
OutPut
Divided Strings....
in finally block...
In except block... handling Type Error...
Programming Tip!
Finally block can never be followed by an except block.

Assertions in Python
An assertion is a sanity-check that you can turn on or turn off when you are done with
your testing of the program. The easiest way to think of an assertion is to liken it to a raise-
if statement (or to be more accurate, a raise-if-not statement). An expression is tested, and if the
result comes up false, an exception is raised.
Assertions are carried out by the assert statement, the newest keyword to Python, introduced in
version 1.5. Programmers often place assertions at the start of a function to check for valid input,
and after a function call to check for valid output.
The assert Statement
When it encounters an assert statement, Python evaluates the accompanying expression,
which is hopefully true. If the expression is false, Python raises an AssertionError exception.
The syntax for assert is −
assert Expression[, Arguments]
If the assertion fails, Python uses ArgumentExpression as the argument for the AssertionError.
AssertionError exceptions can be caught and handled like any other exception using the try-
except statement, but if not handled, they will terminate the program and produce a traceback.
Example
Here is a function that converts a temperature from degrees Kelvin to degrees Fahrenheit. Since
zero degrees Kelvin is as cold as it gets, the function bails out if it sees a negative temperature −
Live Demo
def KelvinToFahrenheit(Temperature):
assert (Temperature >= 0),"Colder than absolute zero!"
return ((Temperature-273)*1.8)+32
print KelvinToFahrenheit(273)
print int(KelvinToFahrenheit(505.78))
print KelvinToFahrenheit(-5)
Output:
32.0
451
Traceback (most recent call last):
File "test.py", line 9, in <module>
print KelvinToFahrenheit(-5)
File "test.py", line 4, in KelvinToFahrenheit
assert (Temperature >= 0),"Colder than absolute zero!"
AssertionError: Colder than absolute zero!

You might also like