Python Programming Inheritance

You might also like

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

Public, Protected and Private members:

- Access modifiers
- It is straight forward in c++ , Java - done using the keywords as public, protected,
private
- There is no direct way or keyword available in python for this purpose, yet its
possible using the convention methods.
- Public
- All member variables and methods are public by default.
- Can access class attributes and modify their values outside

class Employee:
def __init__(self, name, sal):
self.name = name
self.salary = sal

e1 = Employee("Aman", 10000)
print("Before Salary ", e1.salary)
e1.salary = 20000
print("After Salary ", e1.salary)

- Protected
- Standard meaning of protected is, class attributes are accessible within
class and its subclasses
- Prefixing single underscore ” _ ” to instance variables is a way of telling
that these are protected and not for accessing outside the class
- Although they can be modified outside as in the below examples shown
(Hence, the responsible programmer would refrain from accessing and
modifying instance variables prefixed with _ from outside its class)

class Employee:
def __init__(self, name, sal):
self._name = name
self._salary = sal

e1 = Employee("Aman", 10000)
print("Before Salary ", e1._salary)
e1._salary = 30000
print("After Salary ", e1._salary)

23
- Private
- Cant be accessed out side the class

class Employee:
def __init__(self, name, sal):
self.__name = name
self.__salary = sal

e1 = Employee("Aman", 10000)
print("Before Salary ", e1.__salary)

On executing above program…

AttributeError: 'Employee' object has no attribute '__salary'

- Python supports a technique called name mangling. Python performs name


mangling of private variables. Every member with double underscore will be
changed to object._ClassName__variable. If so required, it can still be accessed
from outside the class, but the practice should be refrained as shown below-

print("Before Salary ", e1._Employee__salary)

vars() and dir() built-in functions

- vars() returns dictionary of attributes and their values. It can be applied to class
and objects. When used with objects, returns class attributes and recursively
attributes of classes base class.
- dir() returns list of attributes. It can be applied to class and objects as well. When
used with objects, returns a list of objects attributes and objects class attributes
abd recursively attributes of its classes base class.

24
Python Intricacies:

- Below is an example of calling a class method from another class. Observe the
program and its output-

def display_all(): #global function


print("Inside Display All")

class Employee:
def __init__(self, n='',a=0, s=0): #instance method
self.__name = n
self.__age = a
self.__salary = s

def display_data(self): #instance method


print("Inside Employee display data")
print(self.__name, self.__age, self.__salary)
d1 = Department("CS",101)
d1.display_data() #calling a instance method of a class from
another class

print(Department.sample()) #calling a class method from another class


display_all() #calling global function

class Department:
def __init__(self, t, n): #instance method
self.__title = t
self.__id = n

def display_data(self): #instance method


print("Inside Department display data")
print(self.__title, self.__id)

def sample(): # class method


print("Inside Department sample")
# display_data() # will throw an error

e1 = Employee("Aman", 25, 30000)


e1.display_data()

25
Output of above code is below-

Inside Employee display data


Aman 25 30000
Inside Department display data
CS 101
Inside Department sample
None
Inside Display All

Static Method

- These methods are bound to the class not to the object


- Its is similar to class methods (not instance method) but unlike class
methods, it has nothing to do with class and deals with the parameters
passed to it.
- They can be called using staticmethodFunc()
- Used to group utility functions, to perform a task in isolation

class Mathematics:

def addNumbers(x, y):


return x + y

# create addNumbers static method


Mathematics.addNumbers = staticmethod(Mathematics.addNumbers)

print('The sum is:', Mathematics.addNumbers(5, 10))

- Another way of creating static methods is given below

class Dates:
def __init__(self, date):
self.date = date

def getDate(self):
return self.date

26
@staticmethod
def toDashDate(date):
return date.replace("/", "-")

date = Dates("15-12-2016")
dateFromDB = "15/12/2016"
dateWithDash = Dates.toDashDate(dateFromDB)

if(date.getDate() == dateWithDash):
print("Equal")
else:
print("Unequal")

- @staticmethod decorator, It is an expression that gets evaluated after our


function is defined.

Advantage of static methods-

- Less memory consumption, since in case of an instance method no of coppes of


the method will be equal to number of object.
- Utility functions
- Redability

Operator Overloading-

- Changing meaning of operators


- Depending upon the operands how the operator can behave is defined using
operator overloading techniques.
- So the meaning of operator for user defined data types can be defined.
- Special functions in python are used ( funcition which starts and ends with __ i.e.
a double underscore. ) Read more about the special functions here -
https://docs.python.org/3/reference/datamodel.html#special-method-names

Operators that can be overload in python are -

27
Operator Special function Operator Special function

+ __add__(self, other) < __lt__(self, other)

- __sub__(self, other) > __gt__(self, other)

* __mul__(self, other) <= __le__(self, other)

/ __truediv__(self, other) >= __ge__(self, other)

% __mod__(self, other) == __eq__(self, other)

** __pow__(self, other) != __ne__(self, other)

// __floordiv__(self, other)

Example of operator overloading is below-

class Point:
def __init__(self, x=0,y=0):
self.__x = x
self.__y = y

def display_points(self):
print("Points are: ", self.__x, self.__y)

# def __add__(self, other):


# return self.__x + other.__x, self.__y + other.__y

p1 = Point(2,3)
p1.display_points()

p2 = Point(5,5)
p2.display_points()

print("Combining points are: ", p1 + p2)

Output of the above code is here-

Points are: 2 3
Points are: 5 5

28
Traceback (most recent call last):
File "/Users/mspangtey/Downloads/DSEU 2022/Odd Sem Python/Python
Programs/operator_overloading.py", line 19, in <module>
print("Combining points are: ", p1 + p2)
TypeError: unsupported operand type(s) for +: 'Point' and 'Point'

On un commenting the __add__(self, other) method the output is-

Points are: 2 3
Points are: 5 5
Combining points are: (7, 8)

Code Reuse-

- Reusing the existing code


- Two mechanisms available to do so-
- Containership or Composition
- Used when two classes have a ‘has a’ relationship

Example:

class Department:
def __init__(self,n='',id=0):
self.__name = n
self.__id = id

def display_department(self):
print("Department id is {} and Name is {}".format(self.__id,
self.__name))

class Employee:
def __init__(self, n='', eid= 0):
self.__name = n
self.__eid = eid
self.d_obj = Department('ECE', 28)

def display_employee(self):
print("Employee Name is {}, id is {}".format(self.__name, self.__eid))

29
self.d_obj.display_department()
# print("Employee Name is {}, id is {}, Department is {} and
department id is {}".format(self.__name, self.__eid,
self.d_obj._Department__name, self.d_obj._Department__id))

e1 = Employee("Aman", 101)
e1.display_employee()

- Inheritance
- Used when two classes have a ‘like a’ relationship
- Derived Class can inherit features of Base Class
- Base class is also known as super-class or parent class
- Derived class is also known as subclass or child class
- Derived class object contains all base class data
- Derived class members can access base class members but vice
versa is not true.
- Convention for accessing variables
- var: access anywhere
- _var: access within class and derived class
- __var: access only within class
- Violating the conventions do not throw an error, for for good
practice it should follow the convention
- Accessing __var outside the class is not straight forward like
objectName.__var. This will give error. This is because variable
name gets mangled. So to access such variables the variable is
renames as _ClassName__var. Here ClassName is the class which
contains __var.

Example 1:

- Subclass Sparrow class inherits base class Bird.


- Derived class object can access all the methods of base class.
Example is speak method speak().
- The derived objects first find the speak method in Sparrow class,
once it doesnt find it here it searches the method in the base class.

class Bird:
def speak(self):

30
print("Inside Bird: Speak")

class Sparrow(Bird):
def sing(self):
print("Inside sparrow: sing")

s1 = Sparrow()
s1.sing()
s1.speak()

Output of above sample code:

Inside sparrow: sing


Inside Bird: Speak

Example 2:

- __init__ method is defined in both derived as well as in the base


class.
- In such cases, the derived class method overrides that in the base
class. Or __init__ of derived class has preference over base class
__init__.
- In order to call the base class init method, syntax is
super().__init__(3) or Polygon.__init__(self, 3) .
- super().__init__() should be used in case only one Class is
inherited. ClassName.__init__(self) should be used to call specific
inherited class.

class Polygon:
def __init__(self, n):
self.noOfSides = n
sides = [0 for i in range(self.noOfSides)]

def inputSides(self):
self.sides = [float(input("Enter side "+ str(i+1) + " : ")) for
i in range(self.noOfSides)]

def displaySides(self):
for i in range(self.noOfSides):
print("Side ",i+1, " is",self.sides[i])

31
class Triangle(Polygon):
def __init__(self):
super().__init__(3)
# Polygon.__init__(self, 3)

def findArea(self):
a, b, c = self.sides
s = (a+b+c)/2 # s is semiperimeter
area =(s*(s-a)*(s-b)*(s-c))**.5
print("Area of Triangle is %0.2f" %area)

t1 = Triangle()
t1.inputSides()
t1.displaySides()
t1.findArea()

Output of the above code is:

Enter side 1 : 20
Enter side 2 : 30
Enter side 3 : 40
Side 1 is 20.0
Side 2 is 30.0
Side 3 is 40.0
Area of Triangle is 290.47

32
Types of Inheritance-

Built-in methods to check inheritance:

- issubclass(sub, sup)
- Returns True is sub is derived from sub else returns False
- isinstance(obj, Class)
- Returns True if obj is an object or instance of Class else returns False

print(isinstance(t1, Triangle))
print(isinstance(t1, Polygon))
print(issubclass(Triangle, Polygon))
print(issubclass(Triangle, Polygon))

Output of above code is-

True
True
True

33
True

● In case of multiple Inheritance, the order in which methods are inherited is


defined by pythons method resolution technique. It can be viewed using
ClassName.__mro__
● There is something called diamond problem as shown in the below diagram. Der
class will have one copy of Base -> Derived1 and another copy of Base ->
Derived2. This will result in ambiguity.
● Python linearizes the order from left to right.

Example Below:
class Base:
def display(self):
print("Inside Base")

class Derived1(Base):
def display(Self):
print("Inside Derived 1")
super().display()

class Derived2(Base):
def display(self):
print("Inside Derived2")
super().display()

class Der(Derived1, Derived2):


def display(self):
print("Inside Der")
super().display()
d1 = Der()
d1.display()
print("")
print(Der.__mro__)

34
Abstract Class:

- Classes from which an object cant be created is called abstract class


- For this module named abc is imported and ABC class is inherited.
- A decorator @abstractmethod is used.
- If an abstract class contains only methods marked by the decorator
@abstractmethod, it is called an Interface

from abc import ABC, abstractmethod

class Student(ABC):

def __init__(self, first_name, lastname):


self.first_name = first_name
self.lastname = lastname

@property
def full_name(self):
return f"{self.first_name} {self.lastname}"

@abstractmethod
def getScore(self):
pass

35
class FullTime(Student):
def __init__(self,fn, ln, score):
super().__init__(fn, ln)
self.score = score

def getScore(self):
return self.score

class PartTime(Student):
def __init__(self,fn, ln, credit, hrs):
super().__init__(fn, ln)
self.credit = credit
self.hrs = hrs

def getScore(self):
return self.credit * self.hrs

# s1 = FullTime("Manjeet", "Pangtey", 19)

class ScoreBoard:
def __init__(self):
self.student_list = []

def add_student(self, stu):


self.student_list.append(stu)

def display_students(self):
for i in self.student_list:
print(f"{i.full_name} \t {i.getScore()}")

# st1 = Student("Ram", "Singh") # This line will throw error


sb = ScoreBoard()
sb.add_student(FullTime("Aman", "Kumar", 89))
sb.add_student(FullTime("Kiran", "Sharma", 90))
sb.add_student(FullTime("Sundaram", "Swami", 79))
sb.add_student(PartTime("Keshav", "Rao", 3, 8))
sb.add_student(PartTime("Madhav", "Bhatt", 5, 9))
sb.add_student(PartTime("Keshav", "Rao", 3, 8))

sb.display_students()

36

You might also like