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