Unit 3-OOP
Unit 3-OOP
PYTHON
Introduction
Python is an object-oriented language.
Due to that creation and usage of class and object is
very much easy.
You can implement any of the OOP concepts using
python.
Overview of OOP Terminology
The class statement creates a new class definition. The name of the
class immediately follows the keyword class followed by a colon as
follows:
class ClassName:
'Optional class documentation string'
class_suite
def function(self):
print("This is a message inside the class.”)
print (MyClass)
print (MyClass.__doc__)
OUTPUT:- Guess??
__main__.MyClass
None
Another example
class Employee:
'Common base class for all employees'
empCount = 0
def displayCount(self):
print ("Total Employee %d" % Employee.empCount)
def displayEmployee(self):
print ("Name : ", self.name, ", Salary: ", self.salary)
The variable empCount is a class variable whose value is shared
among all instances of a this class. This can be accessed using an
object of a class from inside the class or outside the class.
You declare other class methods like normal functions with the
exception that the first argument to each method is self. Python
adds the self argument to the list for you; you do not need to
include it when you call the methods.
Creating object of a class
You can create an object of a class using any
variable as an object followed by assignment of
class name.
e.g.
myobjectx = MyClass()
emp1= Employee(“abc”,5000)
The variable "myobjectx" holds an object of the
class "MyClass" that contains the variable and the
function defined within the class called "MyClass".
Accessing object variables
To access the variable inside the newly created
object, following way we can do:
myobjectx.variable
Print (myobjectx.variable)
Output:-??
Blah
You can create multiple different objects of class.
However each may contain its own copy of variable defined within
class.
E.g.:-
myobjectx=MyClass()
myobjecty=MyClass()
myobjecty.variable = “ Blah Blah”
print (myobjectx.variable)
print (myobjecty.variable)
Output:-??
Blah
Blah Blah
More on accessing attributes
You can add, remove, or modify attributes of classes and objects at any
time .
def displayCount(self):
print ("Total Employee %d" % Employee.empCount)
def displayEmployee(self):
print ("Name : ", self.name, ", Salary: ", self.salary)
pt1 = Point()
pt2 = pt1
pt3 = pt1
print (id(pt1), id(pt2), id(pt3)) # prints the ids of the objects
del pt1
del pt2
del pt3
Output:-??
You should import them in your main program file using import
statement.
Class Inheritance
You can create a class by deriving it from a pre-existing class
by listing the parent class in parentheses after the new class
name.
A child class can override data members and methods from the
parent.
Syntax
Derived classes are declared much like their parent class;
however, a list of base classes to inherit from is given after the
class name −
def parentMethod(self):
print (“Calling parent method“)
def getAttr(self):
print ("Parent attribute :", Parent.parentAttr)
def childMethod(self):
print (“Calling child method“)
c = Child()
c.childMethod()
c.parentMethod()
c.setAttr(50)
c.getAttr()
print(Child.__bases__)
Output:-??
Calling child constructor
Calling child method
Calling parent method
Parent attribute : 50
(<class '__main__.Parent’>,)
MultiParent Class
You can derive a class from multiple parent classes
The good reason about it that you may want to have special or
different functionality in your subclass.
Example
class Parent: # define parent class
def myMethod(self):
print ('Calling parent method')
def __str__(self):
return ('Vector (%d, %d)' % (self.a, self.b))
v1 = Vector(2,10)
v2 = Vector(5,-2)
print (v1 + v2) Output:-??
Vector (7, 8)
Data hiding
An object's attributes may or may not be visible outside the
class definition.
def count(self):
self.__secretCount += 1
print (self.__secretCount)
counter = JustCounter()
counter.count()
counter.count()
print (counter.__secretCount)
Output:-??
1
2
Traceback (most recent call last):
File "Class.py", line 91, in <module>
print (counter.__secretCount)
AttributeError: 'JustCounter' object has no attribute
'__secretCount
Python protects those members by internally changing the name
to include the class name.
So modified code is
Example
class JustCounter:
__secretCount = 0
def count(self):
self.__secretCount += 1
print (self.__secretCount)
counter = JustCounter()
counter.count()
counter.count()
print (counter._JustCounter__secretCount)
Output:-??
1
2
2
Decorators
Very powerful and useful tool in Python
def shout(text):
return text.upper()
print(shout(“Hello”))
yell = shout
print(yell(“Hello”))
Output: ??
HELLO
HELLO
Example 2: Functions can be passed as arguments
to other functions.
def inc(x):
return x+1
def dec(x):
return x-1
def operate(func,x):
result = func(x)
return result
operate(inc,3)
operate(dec,3)
Output: ??
4
2
Example 3: Functions can return another function.
def is_called(msg):
print("Greeting from is_called.")
def is_returned(): #Clousure function: Are inner fuctions which
remembers the values even if the outer function has completed its
execution.
print("Hello",msg)
return is_returned
New = is_called("World")
New()
Decorators
Functions and methods are called callable as they can be
called.
def make_pretty(func):
def inner():
print(“I got decorated.”)
func()
return inner
def ordinary():
print(“I am ordinary”)
>>> ordinary()
I am ordinary
>>> #Let’s decorate this ordinary function
>>> pretty = make_pretty(ordinary)
>>> pretty()
I got decorated
I am ordinary
Generally, we decorate a function and reassign it as,
ordinary = make_pretty(ordinary)
@make_pretty
def ordinary():
print(“I am ordinary”)
is equivalent to
def ordinary():
print(“I am ordinary”)
ordinary = make_pretty(ordinary)
Decorating Functions with Parameters
>>> divide(2, 5)
0.4
>>> divide(2, 0)
Traceback (most recent call last):
…
ZeroDivisionError: division by zero
Decorator for above code
def smart_divide(func):
def inner(a, b):
print(“I am going to divide”, a , “and”, b)
if b ==0:
print(“Whoops! Cannot divide”)
return
return func(a, b)
return inner
@smart_divide
def divide(a, b):
return a/b
>>> divide(2, 5)
I am going to divide 2 and 5
0.4
>>> divide(2, 0)
I am going to divide 2 and 0
Whoops! Cannot divide
General decorator that works with any parameters
def works_for_all(func):
def inner(*args, **kwargs)
print(“I can decorate any function.”)
func(*args, **kwargs)
return inner
Chaining Decorators in Python
def star(func):
def inner(*args, **kwargs):
print(“*” * 30)
func(*args, **kwargs)
print(“*” * 30)
return inner
def percent(func):
def inner(*args, **kwargs):
print(“%” * 30)
func(*args, **kwargs)
print(“%” * 30)
return inner
@star
@percent
def printer(msg):
print(msg)
printer(“Hello”)
Decorator for above code
Output: ??
******************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Hello
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
******************************
The above syntax is:
@star
@percent
def printer(msg):
print(msg)
printer = star(percent(printer))
Descriptor
Example:-
def __getattribute__(self, key):
v = object.__getattribute__(self, key)
if hasattr(v, ‘__get__’):
return v.__get__(None, self)
return v
The important points to remember are:
Output:??
PythonforPython
Simple class example
class Car:
fuel_cap = 90
def __init__(self,make,model,fuel_cap):
self.make = make
self.model = model
print(“Inside Car Init method”)
self.fuel_cap = fuel_cap
def __str__(self):
return "{0} model {1} with a fuel capacity of {2}
ltr.".format(self.make,self.model,self.fuel_cap)
car1 = Car("BMW","X7",40)
print(car1)
Output:??
BMW model with X7 with a fuel capacity of 40 ltr.
Example
class Descriptor:
def __init__(self):
self.__fuel_cap = 0
def __get__(self, instance, owner):
return self.__fuel_cap
def __set__(self, instance, value):
if isinstance(value, int):
print(value)
else:
raise TypeError("Fuel Capacity can only be an integer")
if value < 0:
raise ValueError("Fuel Capacity can never be less than zero")
self.__fuel_cap = value
def __delete__(self, instance):
del self.__fuel_cap
Example (Conti…)
class Car:
fuel_cap = Descriptor()
def __init__(self,make,model,fuel_cap):
self.make = make
self.model = model
self.fuel_cap = fuel_cap
def __str__(self):
return "{0} model {1} with a fuel capacity of {2} ltr.".format(self.make, self.model,
self.fuel_cap)
car2 = Car("BMW","X7",40)
print(car2)
Output:??
40
BMW model with X7 with a fuel capacity of 40 ltr.
Multithreading
Multiple threads within a process share the same data space with the
main thread and can therefore share information or communicate with
each other more easily than if they were separate processes.
The method call returns immediately and the child thread starts
and calls function with the passed list of args. When function
returns, the thread terminates.
In addition to the methods, the threading module has the Thread class
that implements threading.
The methods provided by the Thread class are as follows:-
run() − The run() method is the entry point for a thread.
start() − The start() method starts a thread by calling the
run method.
join([time]) − The join() waits for threads to terminate.
Is_alive() − The is_alive() method checks whether a thread
is still executing.
getName() − The getName() method returns the name of a
thread.
setName() − The setName() method sets the name of a
thread.
Creating Thread Using Threading Module
Once you have created the new Thread subclass, you can create an
instance of it and then start a new thread by invoking the start(),
which in turn calls the run() method.
Example
Multithreading.txt
Metaclass
A class in Python defines how the instance of the class will behave.
Syntax:
type(classname, superclasses, attributes_dict)
classname = Any meaningful classname
superclasses = Tuple of base classes(in case of inheritance)
attributes_dict = dictionary of attributes as body of class
Example:
Foo = type(‘Foo’, (), {})
x = Foo()
print (x)
print (Foo())
Output:-??
<__main__.Foo object at 0x7f57b3d8d580>
Example (Conti…)
Foo = type(‘Foo’, (), {‘subject’: ‘python’, ‘instructor’: ‘teacher’})
x = Foo()
Output:-??
print(x.subject,x.instructor) python teacher
print(x) <__main__.Foo object at 0x7f1d1ec09580>
print(Foo) <class '__main__.Foo'>
class MetaTwo(type):
def __init__(self, name, bases, dict):
pass
Example
class MetaCls(type):
"""A sample metaclass without any functionality"""
def __new__(cls, clsname, superclasses, attributedict):
print("clsname:", clsname)
print("superclasses:", superclasses)
print("attrdict:", attributedict)
return super(MetaCls, cls).__new__(cls, \
clsname, superclasses, attributedict)
Output:??
clsname: C
Superclasses: (<class ‘object’>, )
Attrdict: { }
class type: <class ‘__main__.MetaCls’>
Metaclass Inheritance
class MetaCls(type):
"""A sample metaclass without any functionality"""
def __new__(cls, clsname, supercls, attrdict):
print(type(A))
Output:??
<class ‘__main__.MetaCls’>
Iterators
Iterators are objects that can be iterated upon.
Python iterator object must implement two special
methods, __iter__()and __next__(), collectively
called the iterator protocol.
An object is called iterable if we can get an iterator
from it. Most of built-in containers in Python
like: list, tuple, dictionary, set, string etc. are
iterables.
The iter() function (which in turn calls
the __iter__() method) returns an iterator from them.
# define a list
my_list = [4, 7, 0, 3]
# get an iterator using iter()
my_iter = iter(my_list)
## iterate through it using next()
#prints 4
print(next(my_iter))
#prints 7
print(next(my_iter))
## next(obj) is same as obj.__next__()
#prints 0
print(my_iter.__next__())
#prints 3
print(my_iter.__next__())
## This will raise error, no items left
next(my_iter)
class yrange:
def __init__(self, n):
self.i = 0
self.n = n
def __iter__(self):
return self
def next(self):
if self.i < self.n:
i = self.i
self.i += 1
return i
else:
raise StopIteration()
>>> y = yrange(3)
>>> y.next() 0
>>> y.next() 1
>>> y.next() 2
>>> y.next() Traceback (most recent call last): File
"<stdin>", line 1, in <module> File "<stdin>", line
14, in next StopIteration