Python Programming Made Easy (2016)

Chapter 9: Classes ,Objects and Inheritance

A class is a definition of an object. A class is a user defined data type just like int, and an object of this class is just a variable. When we define a class, we define a blueprint for a data type. A class is used to specify the form of an object and it combines data representation and methods for manipulating that data into one neat package. The data and functions within a class are called members of the class.

Namespaces

A namespace is a mapping from names to objects. It is a place where a variable’s name is stored. Python has the following namespaces and searches in the following order.

·                    Local can be inside a function or class method, for example inside def

·                    Enclosed can be its enclosing function, e.g., if a function is wrapped inside another function. The variable is searched in all enclosing functions from inner to outer.

·                    Global refers to the uppermost level. The variable is searched in global modules or for names declared global in a def.

·                    Built-in are special names that Python reserves for itself.

Fig 9.1: Python Namespaces

Defining classes

A class definition starts with the keyword class followed by the class name and a colon; For example we defined the Student data type using the keyword class as follows:

class Student:

        count =0

        # constructor to create an object

    def __init__(self,rollno,marks):

            self.rollno = rollno

            self.marks = marks

    def read (self):      # This function gets the details of a student from the user

             Student.count+=1

    def display (self): # This function prints each student's details

In the above class definition rollno,marks,read() and display() are attributes of the class student.

The init function is the first method and is a special method called constructor or initialization method. It is used to initialize the data members of a class. It is the first piece of code executed in a newly created instance of the class.

It can take any number of arguments and also default arguments.

Objects

An object is created from a class. We declare objects of a class with exactly the same sort of declaration that we declare variables of basic types. Following statements declare two objects of class Student:

S1 =Student();          // Declare s1 of type Student

S2= Student();          // Declare s2 of type Student

Both of the objects s1 and s2 will have their own copy of data members.

Objects of a class have a same structure and behavior. They will only differ regarding their state (values).

Self

The first argument of every class method is always a reference to the current instance of the class named “self”. This is the only difference between a class method and an ordinary function. “self” is not a keyword and is just used for readability and uniformity. It is an instance identificator.

Here is an example using self

class Stock:

    discount=5

    # constructor to create an object

    def __init__(self,icode,price,qty):

        self.icode = icode

        self.price = price

        self.qty = qty

   # Displays item details       

    def display (self):

         print "Item Code:",self.icode

         print "Price: Rs",self.price

         print "Quantity: ",self.qty

In the above example, discount is a class variable whose value would be shared among all instances of this class. They can be accessed as

              Stock.discount

Instance attributes belong to each instance/object of a class. i.e. S1.icode and s2.icode will have 2 different values

ACCESSING ATTRIBUTES

Attributes can be accessed by using the dot operator.

·                                  The getattr(obj, name[, default]) : to access the attribute of object.

·                                  The hasattr(obj,name) : to check if an attribute exists or not.

·                                  The setattr(obj,name,value) : to set an attribute. If attribute does not exist, then it would be created.

·                                  The delattr(obj, name) : to delete an attribute.

getattr(obj1, ‘price')    # Returns value of 'price' attribute

hasattr(obj1, ‘price’)    # Returns true if 'price' attribute exists

setattr(obj1, 'price', 30.56) # Set attribute 'price' at 30.56

delattr(obj1, 'price')    # Delete attribute 'price'

BUILT-IN CLASS ATTRIBUTES

Every Python class keeps following built-in attributes and they can be accessed using dot operator like any other attribute:

·                                  __dict__ : Dictionary containing the class's namespace.

·                                  __doc__ : Class documentation string or None if undefined.

·                                  __name__: Class name.

·                                  __module__: Module name in which the class is defined. This attribute is "__main__" in interactive mode.

·                                  __bases__ : A possibly empty tuple containing the base classes, in the order of their occurrence in the base class list.

__ del()__ or destructor

This function is called when the instance is about to be destroyed. It calls the method - object.__del__(self)

Consider the following command is given

>>>del S1

The above command doesn't directly call S1.__del__(). First the reference count for S1 is decremented by one and __del()__ is called only when S1's reference count reaches zero i.e. when all the variables referenced by S1 have been deleted.

__ str()__

It is a special function which returns the string representation of the objects. It calls the method

object.__str__(self). If the class defines a __str__ method, Python will call it when you call the str() or use print statement. The str() built-in function, when used along with the print statement computes the "informal" string representation of an object.

class Student:

……….

………..

def __str__(self):

         return "This class is about a Student?"

>>> S=Student()

>>> print S

Hello, How are you?

When you give the command to "print S", Python calls str(S) to get the string representation of S. If the class of S has a __str__ method, str(S) becomes a call to S.__str__(). This returns the string to print.

Private variables

“Private” instance variables are not supported in Python. It can be achieved through name mangling. A name is prefixed with two leading underscores and no more than one trailing underscore to make it as a private member.

Class Student:

        Rollno = 121

        Grade = ‘A’

                       __age = 15    # private member

Data hiding is a means to enforce encapsulation. To access this hidden variable, we need to follow the syntax

ClassName.__var-name

Ex:- Student.__age=20

DYMANICALLY ADDING METHODS

Dynamic Class Method Addition

Occasionally you may need dynamically add a function as a method to a class.  This is easily accomplished by assigning the function as an attribute of the class.

def fn(self):

  return id(self), self, type(self)

class A_Class(object):

  def method_a(self):

    return id(self), self, type(self)

A = A_Class()

# Modify the class and add fn as a method

setattr(A_Class, 'method_b', fn)

A.method_a()

# Call the dynamically added method

A.method_b()

Dynamic Instance Method Addition

When we add the method to a class all instances can access it.  If we only want a particular instance to have a method do this:

from types import MethodType

A = A_Class()

setattr(A, fn.__name__, MethodType(fn, A, type(A)))

# Calls the fn method attached to the instance A

A.fn()

# Throws an exception

A.fn()

Static Methods

A static method is callable without an object.

class Student:

@staticmethod

def getStudentcount():

     return count

print Student.getStudentCount()

In the above example, the function getStudentcount () dosen’t use self. They are preceded with a decorator @staticmethod and it is being called without the instance. It eases the readability of the code. Static methods are accessible from either an object or a class.

Destroying Objects (Garbage Collection)

Python automatically allocates and de-allocates memory. The user does not have to preallocate or deallocate memory by hand as one has to when using dynamic memory allocation in languages such as C or C++. Python uses two strategies for memory allocation-reference counting and automatic garbage collection.

i) Reference Counting

Reference counting counts the number of times an object is referenced by other objects in the system. An object's reference count changes as the number of aliases that point to it change. An object's reference count increases when it is assigned a new name or placed in a container (list, tuple or dictionary). The object's reference count decreases when it is deleted with del, its reference is reassigned, or its reference goes out of scope. When an object's reference count reaches zero, Python collects it automatically.

Consider the code given below:

A=5              # an object A is created which is bound to 5.

B=A             # increase in reference count of 5

C[0]={B}    # increase in reference count of 5

del A                     # decrease in reference count of 5

B=10           # decrease in reference count of 5

A class can implement the special method __del__(), called a destructor, that is invoked when the instance is about to be destroyed. Reference counting is extremely efficient but it cannot handle reference cycles. A reference cycle is when there is no way to reach an object but its reference count is still greater than zero. The easiest way to create a reference cycle is to create an object which refers to itself as in the example below:

def reference_cycle():

x=[ ]

x.append(x)

reference_cycle()

In the above example since reference_cycle( ) creates an object x which refers to itself (statement x.append(x)), x will not automatically be freed when the function returns. This will cause the memory that x is using to be held onto until the Python garbage collector is invoked.

ii) Automatic Garbage Collection

In this case garbage collection is a scheduled activity. Python schedules garbage collection based upon a threshold of object allocations and object de-allocations. Python deletes the objects which are not required, may it be built-in types or class instances, through the process named garbage collection.

When the number of allocations minus the number of de-allocations are greater than the threshold number, the garbage collector is run and the unused block of memory is reclaimed. One can inspect the threshold for new objects by loading the gc module and asking for garbage collection thresholds.

Automatic garbage collection will not run if your Python device is running out of memory. In such case, your application will throw exceptions, which must be handled otherwise your application will crash. It occurs more for the number of free objects not the size of objects.

Inheritance

Inheritance allows us to define a class in terms of another class, which makes it easier to create and maintain an application. This also provides an opportunity to reuse the code functionality and fast implementation time.

When creating a class, instead of writing completely new data members and member functions, the programmer can designate that the new class should inherit the members of an existing class. This existing class is called the base class, and the new class is referred to as the derived class. The idea of inheritance implements the IS-A relationship. For example, mammal IS-A animal, dog IS-A mammal hence dog IS-A animal as well and so on.

Forms of inheritance

Single inheritance: When a subclass inherits from only one base class, and there are only 2 classes in this relationship, then it is known as single inheritance.

Fig 10.1: Single Inheritance

Multilevel inheritance: When a subclass inherits from a class that itself inherits from another class, it is known as multilevel inheritance.

Fig 10.2: Multilevel Inheritance

Multiple inheritance: When a subclass inherits from multiple base classes it is known as multiple inheritance.

Fig 10.3: Multiple Inheritance

Hierarchical inheritance: When many subclasses inherit from a single base class it is known as hierarchical inheritance.

Fig 10.4: Hierarchical Inheritance

Hybrid inheritance: Hybrid inheritance combines 2 or more forms of inheritance.

Fig 10.5: Hybrid Inheritance

Base Overloading Methods

S.No

Method

Description

Usage

1

__init__ ( self [,args...] )

Constructor

obj = className(args)

2

__del__( self )
 

Destructor, deletes an object

del obj

3

__repr__( self )
 

Evaluateable string representation

repr(obj)

4

__str__( self )

Printable string representation

str(obj)

5

__cmp__ ( self, x )

Comparison of objects

cmp(obj, x)

Single Inheritance

Person is the base class from which a student derives. It has 2 private data members: person’s name as string and age as an integer. It has 2 methods read() and disp() to get and display person details.

Student class is defined with roll number as an integer and average as float. It has 3 member functions

Ø       readStudent() – internally calls read() of Person class and gets the roll no and average of student.

Ø       disprollno() – displays roll number of student

Ø       getaverage() – returns the average of the student

Fig 10.6: Single Inheritance example

class Person(object):

     # constructor to create an object

        def __init__(self):

            self.name = ""

            self.age = 0

        def get(self):

             self.name = raw_input("Enter the Name:")

             self.age = int(raw_input("Age?"))

        def disp(self):

             print "Name :",self.name,"\t Age :",self.age

class Student(Person):

    # constructor to create an object

        def __init__(self):

            super(Student, self).__init__()

            self.rollno = 0

            self.avg = 0.0

        def readStudent(self):

             # Call get method of parent class

             super(Student,self).get()

             self.rollno = int(raw_input("Enter the Roll Number:"))

             self.avg = float(raw_input("Average?"))

        def disprollno(self):

             print "Roll No. :",self.rollno

        def getaverage(self):

              return self.avg

The class Student extends the class Person. The class student adds 2 new instance variables rollno and avg. In order to add new instance variables, the __init__() method defined in class Person needs to be extended.  This function of subclass Student initializes the name and age attributes of the superclass Person and also creates new attributes rollno and avg.

Let’s execute the following statements

p = Person()

p.get()

s = Student()

s.readStudent()

It would produce the following output.

In the above code segment, the object (p) of class person takes values for instance variables name and age. The object (s) of class student can invoke readstudent() function.

The init function can be extended in in 2 ways.

(i)                By using super() function: The init() method is defined in the base class and is extended in the sub class. Super() function is used to call base class methods which has been extended in derived class.

Ex:- super(Student,self).__init__(self,name,age)

(ii)              By using super class’s name: The name of the base class can also be used to access the method of the base class which has been extended.

Ex:- Person.__init__(self,name,age)

MULTILEVEL INHERITANCE

The above single inheritance can be extended to a multilevel inheritance.  GradStudent is another class which derives from the class Student. So GradStudent indirectly inherits from Person class. It has 2 members subject of graduation and workstatus as a single character y-yes and n-no. It has 3 member functions

Ø       readGrad() – calls readStudent() to get student details (which in turn calls read() of Person class) and also gets the Graduate’s details

Ø       workstatus() – returns the workstatus y or n

Ø       displaySubject() – prints the subject

class GradStudent(Student):

        def __init__(self):

            super(GradStudent, self).__init__()

            self.subject = ""

            self.working = ''

    def readGrad (self):

           # Call readStudent method of parent class

           super(GradStudent, self).readStudent()

           self.subject = raw_input("Enter subject of graduation")

           self.working = raw_input("Working? y/n")

    def workstatus(self):

         return self.working

    def displaysubject (self):

         print "Subject:",self.subject

The same way init() and the other methods can be called using super or the class name.

Multiple Inheritance

Python supports multiple inheritance.  The general syntax of multiple inheritance is given below.

class Derived(Base1, Base2, Base3):

              <statement1>

              ……             

              <statement N>

If an attribute/method is not found in Derived, it is searched in Base1 and only if it is not there it is searched in the subsequent base classes.

Method Overriding

Overriding enables the programmer to provide specific implementation to a method in the derived class. So, the method invoked depends on the object used to invoke it. If base class object is used then base class version of the method is called else the derived class method is called.

·                                  The issubclass(sub, sup) boolean function returns true if the given subclass sub is indeed a subclass of the superclass sup.

·                                  The isinstance(obj, Class) boolean function returns true if obj is an instance of class Class or is an instance of a subclass of Class

Abstract methods

An unimplemented method is called an abstract method. These methods are declared in a parent class, but not implemented in it. The implementation is done in the derived class.

class Shape(object):

    def findArea(self):

        raise NotImplementedError

S = Shape()

S.findArea()

We get the following error

Now, the above class can be extended as below

class Shape(object):

    def findArea(self):

        raise NotImplementedError

class Square(Shape):

    def __init__(self,side):

        self.side =side

    def findArea(self):

        return self.side * self.side

S = Square(10)

print S.findArea()

The output of the program is shown below

When an abstract method is declared in a base class, the derived class has to either define the method or raise “NotImplementedError”.

The error will be raised only if we try to use that method. This might be useful when we do not want to implement certain methods in the derived class. But if we need to ensure that the derived class overrides this method we could use the “abc” module of Python.

import abc

class Shape(object):

    __metaclass__  = abc.ABCMeta

     @abc.abstractmethod

    def findArea(self):

         """Method that should do something."""

class Square(Shape):

    def __init__(self,side):

        self.side =side

    def findArea(self):

        return self.side * self.side

S1 = Shape()

S2 = Square(10)

Here the object S2 doesn’t create a problem because the findArea() method is implemented.

Solved Questions

1. What do you understand about a member function? How does it differ from an ordinary function?

Ans: A member function is part of a class and can be called only with an object. An ordinary function can’t be called by an object. A member function takes “self” as the first argument while an ordinary function doesn’t.

2. Predict the output of the following code snippet

ptr=40

def result():

   print ptr

  ptr=90

       def func(var):

  if var<=60:

              ptr=30

              print ptr

                     result()

              func(60)

                     func(70)

Ans:

UnboundLocalError: local variable 'ptr' referenced before assignment

3. Predict the output of the following code snippet

ptr=50

def result():

  global ptr

  ptr=ptr+1

  print ptr

result()

print ptr

Ans.

51

51

4. Name the methods that can be used to

a. access attribute of an object

b. delete an attribute of an object

Ans.

a.  getattr(obj, name[, default])

b. delattr(obj, name)

5. Give the statement to

a. Check whether the attribute str exists in the class Test whose object is T1

b. Assign a value "Hello" to the attribute str of class Test and object T1.

Ans.

a. hasattr (T1,str)

b. setattr (T1, str, “Hello”)

6. Write a Python program using classes and objects to simulate result preparation system for 20 students.    The data available for each student includes:   Name,   Rollno,   and Marks in 3 subjects. The percentage marks and grade are to be calculated from the following information  

Percentage of marks

Grade

80 to 100

A

60 to 80

B

45 to 60

C

Less than 45

D


                                          Grade formula

Also demonstrate constructor overloading.

Ans.

class Student:

        # constructor to create an object

    def __init__(self,s=None):

        #non-copy constructor

        if s==None:

             self.rollno = 0

             self.name = ''

             self.marks = [0,0,0]

             self.avg = 0.0

             self.grade = ''

        # copy constructor

        else:

            self.rollno = s.rollno

            self.name = s.name

            self.marks = s.marks

            self.avg = s.avg

            self.grade = s.grade

    def read (self):

     # This function gets the details of a student from the user

        self.rollno = raw_input("Enter roll number:-")

        self.name = raw_input("Enter name:-")

        s=0

        for i in range(0,3):

            self.marks[i] = int(raw_input("Enter the marks ?"))

            s =  s + self.marks[i]

        self.avg = s/3

        if(self.avg>60):

            self.grade='A'

        elif self.avg>40:

            self.grade='B'

        else:

            self.grade='C'

     def display (self):

       # This function prints each student's details

       print self.rollno,"\t",self.name,"\t\t",self.grade

= Student()

studlist = []

num = int(raw_input("Enter no. of students:-"))

for i in range(0,num):

      s.read()

      studlist.append(Student(s))             

print " STUDENT REPORT \n"

print "*******************************************\n"

print "RollNo \t Name \t\t Grade"

print "*******************************************\n"

for i in range(0,num):

    studlist[i].display()

#Initialize avg as the first student's avg

maxavg = studlist[0].avg

totavg = 0

for i in range(1,num):

    totavg = totavg + studlist[i].avg

    if studlist[i].avg > maxavg:

        maxavg = studlist[i].avg

        topper = studlist[i].name

totavg = totavg/num

print "Class topper is",studlist[i].name,"with average",studlist[i].avg

print "Class average is",totavg

7. Define a class Travel in Python with the following descriptions :                             

Private Members:

PlanCode of type long

Place of type character array (string)

No_of_travellers of type integer

No_of_buses of type integer

Public Members:

A constructor to assign initial values of Plan Code as 1001, Place as “Agra”,

No_of_travellers as 5, No_of_buses as 1

A function NewPlan( ) which allows user to enter PlanCode, Place and

No_of_travellers. Also, assign the value of No_of_buses as per the

following conditions :

No_of_travellers                                           No_of_buses

Less than 20                                                                                     1

Equal to or more than 20 and less than 40                             2

Equal to 40 or more than 40                                                         3

A function ShowPlan( ) to display the content of all the data members on screen.

Ans:

class Travel:

def __init__(self):

self.PlanCode=1001

self.Place="Agra"

self.No_of_travellers=5;

                              self. No _of_buses=1;

def NewPlan(self):

self.PlanCode=input("Enter plan code")

self.Place=raw_input("Enter Place")

self. No_of_travellers=input("Enter No. of travellers")

if(self. No_of_travellers<20):

   self. No _of_buses=1;

elif (self. No_of_travellers<40):

    self. No_of_buses=2;

else:

     self. No _of_buses=3;

def ShowPlan(self):

       print self.PlanCode,self.Place,self.No_of_travellers,self.No_of_buses

t=Travel()

t.NewPlan()

t.ShowPlan()

8. Define the term inheritance.

Ans. Inheritance is a mechanism in which a new class is derived from an already defined class. The derived class is known as a subclass or a child class. The pre-existing class is known as base class or a parent class or a super class. The mechanism of inheritance gives rise to hierarchy in classes. The major purpose of inheriting a base class into one or more derived class is code reuse. The subclass inherits all the methods and properties of the super class.

9. Give one example for abstract method?

Ans. An abstract method is a method declared in a parent class, but not implemented in it. The implementation of such a method can be given in the derived class.

class circle(object):

              def get_radius(self):

10. What is single inheritance?

Ans. : In single inheritance a subclass is derived from a single base class.

11. What is multiple inheritance? Explain with an example.

Ans. : In this type of inheritance, the derived class inherits from one or more base classes. In the figure below, X and Y are the base classes while Z is the derived class.

12. Give one example of multilevel inheritance.

Ans. : In multilevel inheritance, the derived class becomes the base of another class.

Ex:  Country is the base class of State and City is the derived class of State.

13. Define a class employee in Python with the given specifications:

Instance variables:

Employee number, name

Methods:

Getdata()- To input employee number and name

Printdata()- To display employee number and name

Define another class payroll, which is derived from employee

Instance variable

Salaryomputer Science

Methods:

Inputdata() - To call Getdata() and input salary.

Outdata() - To call printdata() and to display salary.

Define another class leave, which is derived from payroll.

Instance variable

No of days

Methods:

acceptdata() - To call Inputdata() and input no of days.

showdata() - To call Outdata() and to display no of days.

Implement the above program in python.

Ans.

class Employee(object):

# constructor to create an object

       def __init__(self):

               self.eno=0

               self.name=''

       def Getdata(self):

          self.eno=input("Enter the Employee no:")

          self.name= raw_input("Enter the Employee Name:")

       def Printdata(self):

           print "Employee Number",self.eno

           print "Employee Name",self.name

class payroll(Employee):

       def __init__(self):

           super(payroll,self).__init__()

           self.salary=0

       def Inputdata(self):

            self.Getdata()

            self.salary=float(raw_input("Enter the salary:"))

       def Outdata(self):

            self.Printdata()

            print "Salary is",self.salary

class leave(payroll):

    def __init__(self):

           super(leave,self).__init__()

           self.Noofdays=0

    def acceptdata(self):

        self.Inputdata()

        self.Noofdays =input("Enter leave days")

    def showdata(self):

        self.Outdata()

        print "No. of leave days",self.Noofdays

leaveobj = leave()

leaveobj.acceptdata()

leaveobj.showdata()

Practice Questions

1.            How are objects destroyed in Python?                                                                      

2.            Can we add methods dynamically in Python? Explain with an example. 

3.            Explain the importance of self in Python classes.                            

4.            Define a class ITEMINFO in Python with the following description:

ICode (Item Code), Item (Item Name), Price (Price of each item), Qty (quantity in stock), Discount (Discount percentage on the item), Netprice (Final Price)

Methods

A member function FindDisc( ) to calculate discount as per the following rule:

If Qty<=10 Discount is 0

If Qty (11 to 20) Discount is 15

If Qty>=20 Discount is 20

A constructor( __init__ method) to assign the value with 0 for ICode, Price, Qty, Netprice and Discount and null for Item respectively

A function Buy( ) to allow user to enter values for ICode, Item, Price, Qty and call function FindDisc( )to calculate the discount and Netprice(Price*Qty-Discount).

A Function ShowAll( ) to allow user to view the content of all the data members

5. Define a class Bank in Python with the following description:                                                        Data members: Name of the depositor: String, Account number:long, Type of account (Savings or current):character, Balance amount:decimal.

Member Functions:

Initialize () – It reads the customer name, account number; account type and current balance and initializes the customer object.

Deposit () – Increases balance amount.

Withdraw () – Checks for minimum balance in current account and decreases the balance accordingly.

Display () – Displays the customer details.

6. What is the use of the init() function in inheritance? Explain the 2 ways in which it could be invoked.

7. What is the significance of super( ) function?

8. What are abstract methods? How do we implement abstract methods in Python.   Give an example.

9. Find the output of the following code and write the type of inheritance:

class person(object):

def __init__(self,name):

self.name=name

def display(self):

print "Name :",self.name

class student(person):

def __init__(self,name,rollno,marks):

super(student,self).__init__(name)

self.rollno=rollno

self.marks=marks

def disp(self):

self.display()

print " Roll No:",self.rollno

print " Marks :",self.marks

s=student(‘Kali’,11009,78)

s.disp()

10. Define a class furniture in Python with the given specifications:

Instance variables:  Type,Model,

Methods: getType() - To return instance variable Type

getModel()- To return instance variable Model

show() To print instance variable Type and Model

Define a class sofa:

No_of_seats ,Cost

Methods

getSeats() To return instance variable No_of_seats

getCost() To return instance variable Cost

show() To print instance variable No_of_seats and Cost

This class inherits class furniture.