Step 1 1f Object Oriented Python
Step 1 1f Object Oriented Python
Object Oriented Programming is a paradigm of programming used to represent real-world objects in programs.
Real-world objects have certain properties and behaviours. These are modelled with Classes and Objects in
Python.
Properties or Data Fields capture the state of real-world objects and Methods capture the behavior of real-world
objects
Fig 1 - Instantiating
In [ ]:
# create an empty class called `Person`
# (NOTE: class names always begin with a capital letter!)
class Person:
pass # empty block
# Print `p`
print(p)
Attributes of a Class
(Data) Fields : Variables that belong to an object or class are referred to as fields
Objects can store data using ordinary variables that belong to the object
Methods: Objects can also have functionality by using functions that belong to a class
Such functions are called methods of the class
Collectively, the fields and methods can be referred to as the attributes of that class.
Fig 2a - Data Fields (Properties) and Methods
In [ ]:
# create a class called `Person`
class Person:
# create a new built in method called say_hi
def say_hi(self):
print('Hello, how are you?')
Notice that the say_hi method takes no parameters when calling it in the object
but still has the self in the class function definition
self keyword
Class methods have only one specific difference from ordinary functions
they must have an extra first parameter that has to be added to the beginning of the parameter list
by convention, it is given the name self .
do not give a value for this parameter when you call the method
Python will automatically provide it
this particular variable refers to the object itself
Data Fields
we have already discussed the functionality part of classes and objects (i.e. methods),
now let us learn about the data part.
the data part, i.e. fields, are nothing but ordinary variables that are bound to the namespaces of the classes
and objects
recall scope of variables in functions, this works similar to that, but at class and object levels
There are many method names which have special significance in Python classes
We will see the significance of the __init__ method
The __init__ method is run as soon as an object of a class is instantiated (i.e. created)
The method is useful to do any initialization you want to do with your object
i.e. passing initial values to your object variables
In [ ]:
# create Class called `Person`
class Person:
The variables under the __init__ function have to be passed as arugments when creating the object from
the class
When creating new instance p , of the class Person
we do so by using the class name, followed by the arguments in the parentheses
We define the __init__ method as taking a parameter name
We do not explicitly call the init method, this is the special significance of this method
self.name means that there is something called "name" that is part of the object called "self"
the other name is a local variable
class variable :
object variable :
ASIDE #1:
Docstrings :
these are string literals that appear right after the definition of a function, method, class, or module
they appear when help(function_name) is run
In [ ]:
# Docstrings Example
def is_even(num):
# Docstring
"""
Input: an integer
Output: if input is even (True for even, False for not)
"""
return num % 2 == 0
help(is_even)
ASIDE #2:
.format()
used to fill in the blanks of a string in the print() statement
In [ ]:
# `.format()` example
pi_value = 3.14
print("The value of Pi is {} ".format(pi_value))
In [ ]:
# Define a class called Robot
class Robot:
"""Represents a robot, with a name.""" # Docstrings
Robot.population -= 1
if Robot.population == 0:
print("{} was the last one.".format(self.name))
else:
print("There are still {:d} robots working.".format(
Robot.population))
the name variable belongs to the object (it is assigned using self ) and hence is an object variable
we refer to the object variable name using self.name notation in the methods of that object
NOTE: an object variable with the same name as a class variable will hide the class variable!
instead of Robot.population , we could have also used self.__class__.population because every
object refers to its class via the self.__class__ attribute
object methods
docstrings
in this program, we see the use of docstrings for classes as well as methods
we can access the class docstring using Robot.__doc__ and the method docstring as
Robot.say_hi.__doc__
constructor function
observe that the __init__ method is used to initialize the Robot instance with a name
in this method, we increase the population count by 1 since we have one more robot being added
also observe that the values of self.name is specific to each object which indicates the nature of
object variables
class variable
class function
the how_many is actually a method that belongs to the class and not to the object
this means we can define it as either a classmethod or a staticmethod depending on whether we need
to know which class we are part of
since we refer to a class variable , let's use classmethod
we have marked the how_many method as a class method using a decorator
decorators can be imagined to be a shortcut to calling a wrapper function (i.e. a function that "wraps"
around another function so that it can do something before or after the inner function), so applying the
@classmethod decorator is the same as calling:
how_many = classmethod(how_many)
In [ ]:
# Using the above Robot Class setup
#------------------------------------------------------
# Initialize Robot 1 called 'R2-D2' in `droid1`
droid1 = Robot("R2-D2")
#------------------------------------------------------
# Initialize Robot 2 called 'C-3PO'
droid2 = Robot("C-3PO")
#------------------------------------------------------
# Print a note about Robots working
print("\n Robots can do some work here.\n ")
#------------------------------------------------------
# Use Built-In Object Functions
droid1.die()
droid2.die()
Inheritance
a major benefit of OOP is reuse of code
one way this is achieved is through inheritance mechanism
inheritance can be best imagined as implementing a type (parent class) and subtype (child class)
relationship between classes
School Members Example
Problem Statement:
suppose you want to write a program for a college which has to keep track of
teachers
students
they both have some common characteristics such as
name,
age and
address
they also have specific characteristics such as
for teachers
salary
courses
leaves
for students
marks
fees
Solution Strategy:
independent classes
you can create two independent classes for each type ( teachers and students), but adding a new common
characteristic would mean adding to both of these independent classes
this quickly becomes unwieldy
inherited classes
a better way would be to create a common class called SchoolMember and then have the teacher and
student classes inherit from this class
i.e. they will become sub-types (child classes) of this type (parent class) and then we can add specific
characteristics to these sub-types
there are many advantages to this approach
In [ ]:
class SchoolMember :
'''Represents any school member.'''
def __init__(self, name, age):
self.name = name
self.age = age
print('(Initialized SchoolMember: {} )'.format(self.name))
def tell(self):
'''Tell my details.'''
print('Name:"{} " Age:"{} "'.format(self.name, self.age), end=" ")
In [ ]:
class Teacher (SchoolMember):
'''Represents a teacher.'''
def __init__(self, name, age, salary):
SchoolMember.__init__(self, name, age)
self.salary = salary
print('(Initialized Teacher: {} )'.format(self.name))
def tell(self):
SchoolMember.tell(self)
print('Salary: "{:d}"'.format(self.salary))
In [ ]:
class Student (SchoolMember):
'''Represents a student.'''
def __init__(self, name, age, grade):
SchoolMember.__init__(self, name, age)
self.grade = grade
print('(Initialized Student: {} )'.format(self.name))
def tell(self):
SchoolMember.tell(self)
print('Grade: "{:d}"'.format(self.grade))
In [ ]:
# initialize `t` - teacher object
t = Teacher('Mrs. Alyssa', 40, 30000)
# name: Mrs. Alyssa
# age: 40
# salary: 30000
members = [t, s]
for member in members:
# Works for both Teachers and Students
member.tell()
Explanation of Inheritance
to use inheritance, we specify the parent-class names in a tuple following the class name in the class
definition
for example, class Teacher(SchoolMember)
next, we observe that the __init__ method of the parent-class is explicitly called using the self variable
so that we can initialize the parent class part of an instance in the child-class
since we are defining a __init__ method in Teacher and Student subclasses, Python does not
automatically call the constructor of the child-class SchoolMember , you have to explicitly call it
yourself
in contrast, if we had not defined an __init__ method in the child-classes, Python will call the
constructor of the parent-class automatically
while we could treat instances of Teacher or Student as we would an instance of SchoolMember and
access the tell method of SchoolMember by simply typing Teacher.tell or Student.tell , we instead
define another tell method in each child-class (using the tell method of SchoolMember for part of it)
to tailor it for that subclass
because we have done this, when we write Teacher.tell , Python uses the tell method for that
child-class vs the parent-class
however, if we did not have a tell method in the child-class, Python would use the tell method in
the parent-class
Python always starts looking for methods in the actual child-class type first
and if it doesn’t find anything, it starts looking at the methods in the child-class's parent-classes,
one-by-one in the order they are specified in the tuple in the class definition
here we only have 1 base class, but you can have multiple parent classes
a note on terminology - if more than one class is listed in the inheritance tuple, then it is called multiple
inheritance
the end parameter is used in the print function in the parent-class's tell() method to print a line and
allow the next print to continue on the same line
this is a trick to make print not print a \n (newline) symbol at the end of the printing
Encapsulation
an object's variables should not always be directly accessible
to prevent accidental change, an object's variables can sometimes only be changed with an objects
methods
those type of variables are private variables
the methods can ensure the correct values are set
if an incorrect value is set, the method can return an error
Python does not have the private keyword, unlike some other object oriented languages, but encapsulation
can be done
to achieve this, it relies on the convention
a class variable that should not directly be accessed should be prefixed with an underscore
In [ ]:
## Encapsulation Example
# constructor function
def __init__(self):
self.a = 123 # public object variable
self._b = 123 # private object variable (by convention - not a hard variable)
self.__c = 123 # Python enforced private variable
an attempt to access the Python enforced private variable yields an error as seen above
single underscore :
Private variable, it should not be accessed directly. But nothing stops you from doing that (except
convention).
More specifically, a "protected" variable, if drawing parallels to other programming langauages
double underscore :
Private variable, harder to access but still possible.
Both are still accessible: Python only has private variables by convention.
In [ ]:
# initialize a new Robot class
class Robot:
# constructor function
def __init__(self):
self.__version = 22
# getter function
def getVersion(self):
print(self.__version)
# setter function
def setVersion(self, version):
self.__version = version
In [ ]:
In [ ]:
Encapsulation Boundaries
private properties do not extend to child classes, or the public object
protected properties extend to child classes, but not the public object
when overriding with functions in the child classes,
the access level can be same or weaker
not stronger
once the object is created
only public attributes and methods can be accessed
Abstract Classes
an abstract class can be considered as a blueprint for other classes
it allows you to create a set of methods that must be created within any child classes built from the parent
abstract class
a class which contains one or more abstract methods is called an abstract class.
an abstract method: a method that has a declaration but does not have an implementation
while we are designing large functional units we use an abstract class.
when we want to provide a common interface for different implementations of a component, we use an
abstract class
In [ ]:
In [ ]:
# Create Objects from Children Classes
In [ ]:
# Python program invoking a
# method using super()
In [ ]:
Abstract Properties
abstract classes includes attributes in addition to methods
you can require the attributes in concrete classes by defining them with @abstractproperty
In [1]:
# Python program showing abstract properties
import abc
from abc import ABC, abstractmethod
try:
new_object = parent()
print(new_object.geeks)
except Exception as err:
print (err)
new_object = child()
print(new_object.geeks)
in the above example, the Parent class cannot be instantiated because it has only an abstract version of the
property getter method
In [ ]:
class Animal(ABC):
@abstractmethod
def move(self):
pass
class Human(Animal):
def move(self):
print("I can walk and run")
class Snake(Animal):
def move(self):
print("I can crawl")
class Dog(Animal):
def move(self):
print("I can bark")
class Lion(Animal):
def move(self):
print("I can roar")
cat = Animal()
Interface
it is like abstract classes but allows child classes to implement multiple classes
cannot define variables (data members) in an interface or constructor functions
only abstract methods
only public functions can be defined, no private and protected functions can be defined
an interface is a set of publicly accessible methods on an object which can be used by other parts of the
program to interact with that object
Interfaces set clear boundaries and help us organize our code better
In [ ]:
## Create class called Team
class Team:
# define constructor
def __init__(self, members):
self.__members = members
## Size protocol
print(len(justice_league_fav))
## Membership protocol
print("batman" in justice_league_fav)
print("superman" in justice_league_fav)
print("cyborg" not in justice_league_fav)
in the above example, by implementing the __len__ and __contains__ method, we can now directly use
the len function on a Team instance and check for membership using the in and not in operators
so we can see that protocols are like informal interfaces
In [2]:
import abc
class Bird(abc.ABC):
@abc.abstractmethod
def fly(self):
pass
if any class derives from our base Bird class, it must implement the fly method too
In [ ]:
class Parrot(Bird):
def fly(self):
print("Flying")
p = Parrot()
since our parrot is recognized as an instance of Bird ABC, we can be sure from it’s type that it definitely
implements our desired interface
In [ ]:
class Aeroplane(abc.ABC):
@abc.abstractmethod
def fly(self):
pass
class Boeing(Aeroplane):
def fly(self):
print("Flying!")
b = Boeing()
isinstance(p, Aeroplane)
isinstance(b, Bird)
In [6]:
class Banana(abc.ABC):
@abc.abstractmethod
def banana_one(self):
pass
def apple_one(self):
print("Child Class - Apple")
def banana_one(self):
print("Child Class - Banana")
In [9]:
## instantiate objects
fruit_bowl = Fruits()
fruit_bowl.apple_one() # call function initialized in Apple Base Class
fruit_bowl.banana_one() # call function initialized in Banana Base Class
Reference
Swaroop's Blog
Encapsulation in Python
Abstract Base Classes - Python Docs
Abstract Classes
Informal Interface
Abstract Base Classes for Containers - Python Docs