Object Oriented Programming#
Programming with functions/procedures is referred to as procedural programming
Sufficient for most engineering computational tasks
Object Oriented Programming (OOP)
A programming paradigm where computations are centered around objects
Objects combine:
Data – referred to as attributes
Functions – referred to as methods
Enables description of real-world objects computationally
A class is a blueprint/template for creation of objects of a certain type
Objects are instances of classes
Python is a multi-paradigm language
Supports both procedural and OOP
Everything in Python is in fact an object of a specific type
E.g.,
int
,complex
,str
,list
,function
,module
Other multi-paradigm languages
C++, Java, Matlab, C# etc.
C and Fortran (pre-2003) do not support OOP
OOP assumes that a programmer implements classes for other programmers to use
Key principles of OOP
Encapsulation
Mechanism for combining data with functions and restricting access to the object’s components
Enables redefinition/reconfiguration of the class without affecting the user (provided the mechanisms to access data and the object’s behavior remains the same)
Key principles of OOP
Inheritance
Mechanism to extend/reuse classes
Enables creation of new derived classes from base classes
Polymorphism
In the OOP context, it refers to a mechanism that determines which class method is to be used in a hierarchy of derived classes
A more general concept where an operator is agnostic to the data types it operates on (e.g., the
len
function, +/* operators)
Python Classes#
Classes in Python are defined using the
class
statementWith an optional base class, the class becomes a derived class and inherits attributes and methods from the base class
Multiple inheritance from more than one base class is possible
An indented block of Python statements (typically, function definitions) form the body of the class definition
class ClassName(OptionalBaseClassName):
'''Descriptive String'''
#class body
statement 1
statement 2
.
.
statement n
The class itself is an object in python
A variable when defined creates an instance of a specific class
e.g
5
is an instance of the classint
e.g.
[1, 3, 5]
is an instance of the classlist
type
andisinstance
functions used to determine class membership
The classes have many methods (special functions) that can be used by instances of the class
e.g.
append
is a method for listsdir
function used to see all methods of a class
print(dir(list))
['__add__', '__class__', '__class_getitem__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getstate__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
Example: create a custom Complex class
Note: Python classes can be modified after the initial definition
Not recommended
class Complex():
'''My complex class'''
def __init__(self, real = 0, imag = 0):
'''Initialization method'''
self.re = real
self.im = imag
self.abs = (real**2 + imag**2)**0.5
def __str__(self):
'''String form of the object'''
if self.im < 0:
return f"({self.re} - {abs(self.im)}j)"
return f"({self.re} + {self.im}j)"
def GetAbsVal(self):
'''Accessor for absolute value'''
return self.abs
def conjugate(self):
'''Returns the complex conjugate'''
return Complex(self.re, -self.im)
Definition following the common convention
Typically, all the statements in a class definition are method definitions
The first parameter for methods is always named
self
and is the object itself__init__
is a special method that is called when an object is to be initializedInitial object state (attribute values) is defined in this method
__str__
is another special method that returns the string format of the objectUsed by the
print
andstr
function if defined
The double underscore methods are for internal use and not to be used with the objects by convention
Accessor methods (GetAbsVal is an example here) to get and set attributes, typically
Set methods are used to alter the state of the object
c1 = Complex(5, -2)
c2 = c1.conjugate()
print(c1)
print(c2)
(5 - 2j)
(5 + 2j)
The
self
argument for any method call is automatically included
Additional attributes and methods can be added to a specific object
# Instance attribute (specific to object c1)
c1.phase = 0
Default Behavior#
Python provides all new classes some default behavior for a few special methods like
__init__
,__class__
etc.This behavior can be redefined through a mechanism known as overloading
c1 = Complex(1, 2)
c2 = Complex(3, -1)
c1 + c2
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[5], line 3
1 c1 = Complex(1, 2)
2 c2 = Complex(3, -1)
----> 3 c1 + c2
TypeError: unsupported operand type(s) for +: 'Complex' and 'Complex'
Leads to an exception as
+
operator is not overloaded
Operator overloading
Define/redefine what an operator does with your object(s)
Need to overload the special methods:
__add__
for+
(addition)__sub__
for-
(subtraction)__mul__
for*
(multiplication)__truediv__
for/
(division)…
class Complex:
def __init__(self, re = 0, im = 0):
self.re = re
self.im = im
self.abs = (re**2 + im**2)**0.5
def __str__(self):
if self.im < 0:
return f"{self.re} - {-self.im}j"
return f"{self.re} + {self.im}j"
def __add__(self, other):
return Complex(self.re + other.re, \
self.im + other.im)
def __sub__(self, other):
return Complex(self.re - other.re, \
self.im - other.im)
def conjugate(self):
return Complex(self.re, -self.im)
c1 = Complex(1, 2)
c2 = Complex(3, -1)
print(c1 + c2)
print(c1 - c2)
Inheritance#
Subclasses (i.e., derived classes) inherit methods and functionality from superclasses (i.e., parent classes)
super()
function allows access to superclass methodsRedefining a method in the subclass will override the definition from the superclass
Example: a
Shape
class for 2D shapesThis will serve as the base or parent class
class Shape:
def __init__(self, name):
self.name = name
def __str__(self):
return self.name
def area(self):
print("Placeholder")
def fact(self):
return "A two-dimensional shape"
Derived class
Square
from the base classShape
Overrides the
__init__
,area
andfact
methodsInherits the
__str__
method from the base class
class Square(Shape):
def __init__(self, length):
super().__init__("Square")
self.length = length
def area(self):
return self.length**2
def fact(self):
return "Square has 4 sides"
a = Square(4)
print(a)
print(a.area())
print(a.fact())
Derived class
Square
from the base classShape
Overrides the
__init__
andarea
methodsInherits the
__str__
andfact
methods from the base class
class Circle(Shape):
def __init__(self, radius):
super().__init__("Circle")
self.radius = radius
def area(self):
return 3.14159*self.radius**2
b = Circle(2)
print(b)
print(b.area())
print(b.fact())
When a method is called on an object, the Python interpreter looks for the method definition in the derived class first and then the base class - this is polymorphism