Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
0% found this document useful (0 votes)
45 views

Module 4-Classes, Objects &inheretace-Updated

Copyright
© © All Rights Reserved
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
45 views

Module 4-Classes, Objects &inheretace-Updated

Copyright
© © All Rights Reserved
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
You are on page 1/ 112

Module 4

Classes and objects

1 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Think Python
How to Think Like a Computer Scientist
-Allen Downey
15. Classes and objects
16. Classes and functions
17. Classes and methods
18. Inheritance

2 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Object-oriented programming
Python is an object-oriented programming language,
which means that it provides features that support
object-oriented programming ( OOP).
Object-oriented programming has its roots in the 1960s,
but it wasn’t until the mid 1980s that it became the main
programming paradigm used in the creation of new
software.
It was developed as a way to handle the rapidly
increasing size and complexity of software systems, and
to make it easier to modify these large and complex
systems over time.
Up to now we have been writing programs using a
procedural programming paradigm.
3 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Difference between Procedure Oriented and
Object Oriented Programming
Procedural programming creates a step by step
program that guides the application through a sequence
of instructions. Each instruction is executed in order
In procedural programming the focus is on writing
functions or procedures which operate on data.
Object-Oriented programming is much more similar to
the way the real world works; it is analogous to the
human brain. Each program is made up of many entities
called objects.
In object-oriented programming the focus is on the
creation of objects which contain both data and
functionality together.

4 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Features of OOP

Ability to simulate real-world event much more


effectively
Code is reusable thus less code may have to be written
Programmers are able to produce faster, more accurate
and better written applications
Data becomes active
Better able to create GUI (graphical user interface)
applications

5 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


The four major principles of
object orientation
Encapsulation
 Data Abstraction
Inheritance
Polymorphism

6 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


What is an Object..?
Objects are the basic run-time entities in an object-
oriented system.
They may represent a person, a place, a bank account,
a table of data or any item that the program must
handle.
When a program is executed the objects interact by
sending messages to one another.
Objects have two components:
Data (i.e., attributes)
 Behaviors (i.e., methods)

7 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Object Object

Class

Belongs To
Belongs To
Uses
Uses

Uses
Uses

Belongs To
Belongs To

Data

Object
Object

8 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Programmer-defined types
(User-defined compound types)
We have used many of Python’s built-in types; now we are
going to define a new type.
A class in essence defines a new data type.
Lets create our own user-defined type: Point. that
represents a point in two-dimensional space.
Consider the concept of a mathematical point.
In two dimensions, a point is two numbers (coordinates)
that are treated collectively as a single object.
In mathematical notation, points are often written in
parentheses with a comma separating the coordinates.
For example, (0, 0) represents the origin, and (x, y)
represents the point x units to the right and y units up from
the origin.
9 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
User-defined compound types
A natural way to represent a point in Python is with
two numeric values.
The question, then, is how to group these two values
into a compound object.
The quick and dirty solution is to use a list or tuple,
and for some applications that might be the best
choice.
An alternative is to define a new user-defined
compound type, also called a class.
This approach involves a bit more effort, but it has
advantages that will be apparent soon.

10 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Class
A class is the blueprint from which individual objects
are created.
In object-oriented programming, a class is an
extensible program-code-template for creating
objects, providing initial values for member
variables and implementations of methods.

11 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Class Definition
A class definition looks like this:

 Class definitions can appear anywhere in a program, but they are


usually near the beginning (after the import statements).
 The syntax rules for a class definition are the same as for other
compound statements.
 There is a header which begins with the keyword, class, followed
by the name of the class, and ending with a colon.
 This definition creates a new class called Point.

12 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Instantiation
The members of this type are called instances of the type or objects.
Creating a new instance or objects is called instantiation, and is accomplished by
calling the class.
An Object is an instance of a class.
Classes, like functions, are callable, and we instantiate a Point object by calling the
Point class:
O/P when you
just type Point

The variable blank is assigned a reference to a new Point object.


It may be helpful to think of a class as a factory for making objects, so our Point
class is a factory for making points.
When you print an instance, Python tells you what class it belongs to and where it is
stored in memory (the prefix 0x means that the following number is in hexadecimal).

13 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Attributes
Like real world objects, object instances have both
data members (features or attributes) and
function.
We can add new data elements to an instance using dot
notation:
This syntax is similar to the syntax for selecting a
variable from a module, such as math.pi or
string.uppercase.
blank.x=3.0
blank.y=4.0
These elements(x and y) are called attributes

14 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Attributes
The following object diagram shows the result of these
assignments:

The variable blank refers to a Point object, which contains


two attributes.
Each attribute refers to a number.
We can read the value of an attribute using the same syntax:
>>> blank.y
4.0
>>> x = blank.x
>>> x
3.0
15 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Attributes
You can use dot notation as part of any expression, so the
following statements are legal:
>>> '(%g, %g)' % (blank.x, blank.y)
'(3.0, 4.0)'
>>> distance = math.sqrt(blank.x**2 + blank.y**2)
>>> distance
5.0

16 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Attributes
You can pass an instance as an argument in the usual
way. For example:
def print_point(p):
print('(%g, %g)' % (p.x, p.y))

print_point takes a point as an argument and displays it


in mathematical notation. To
invoke it, you can pass blank as an argument:
>>> print_point(blank)
(3.0, 4.0)

17 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Point class
>>class Point:
"""Represents a point in 2-D space."
With attributes x & y"“

>>blank = Point()
>>blank.x = 3.0
>>blank.y = 4.0
>>def print_point(p):
print('(%g, %g)' % (p.x, p.y))
>>print_point(blank)
>> def distace(p):
print(math.sqrt(p.x**2 + p.y**2))
>> distace(blank) 18 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Rectangles
Imagine you are designing a class to represent
rectangles.
What attributes would you use to specify the location
and size of a rectangle? You can ignore angle; to keep
things simple, assume that the rectangle is either
vertical or horizontal.
There are at least two possibilities:
 You could specify one corner of the rectangle (or the center),
the width, and the height.
 You could specify two opposing corners.

19 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Rectangles
class Rectangle:
"""Represents a rectangle.
attributes: width, height, corner.
""“
To represent a rectangle, you have to instantiate a Rectangle
object and assign values to the attributes:

box = Rectangle()
box.width = 100.0
box.height = 200.0
box.corner = Point()
box.corner.x = 0.0
box.corner.y = 0.0
20 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Instances as return values
Functions can return instances. For example, find_center
takes a Rectangle as an argument and returns a Point that
contains the coordinates of the center of the Rectangle:
def find_center(rect):
p = Point()
p.x = rect.corner.x + rect.width/2
p.y = rect.corner.y + rect.height/2
return p
Here is an example that passes box as an argument and
assigns the resulting Point to center:
>>> center = find_center(box)
>>> print_point(center)
(50, 100)
21 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Rectangles
>>>class Rectangle:
"""Represents a rectangle.
attributes: width, height, corner.""“

>>>box = Rectangle()
>box.width = 100.0
>box.height = 200.0
>>box.corner = Point()
>>box.corner.x = 0.0
>>box.corner.y = 0.0
>>def find_center(rect):
p = Point()
p.x = rect.corner.x + rect.width/2
p.y = rect.corner.y + rect.height/2
return p
>> center = find_center(box)
>>> print_point(center)
22 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Objects are mutable
You can change the state of an object by making an
assignment to one of its attributes.
For example, to change the size of a rectangle without
changing its position, you can modify the values of
width and height:

box.width = box.width + 50
box.height = box.height + 100

23 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Objects are mutable
You can also write functions that modify objects. For example,
grow_rectangle takes a Rectangle object and two numbers, dwidth
and dheight, and adds the numbers to the width and height of the
rectangle:

def grow_rectangle(rect, dwidth, dheight):


rect.width += dwidth
rect.height += dheight

Here is an example that demonstrates the effect:


>>> box.width, box.height
(150.0, 300.0)
>>> grow_rectangle(box, 50, 100)
>>> box.width, box.height
(200.0, 400.0)
Inside the function, rect is an alias for box, so when the function
modifies rect, box changes
24 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Copying
Aliasing can make a program difficult to read because
changes in one place might have unexpected effects in
another place.
It is hard to keep track of all the variables that might
refer to a given object.
Copying an object is often an alternative to aliasing.
The copy module contains a function called copy that
can duplicate any object:

25 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Copying
>>> p1 = Point()
>>> p1.x = 3.0
>>> p1.y = 4.0
>>> import copy
>>> p2 = copy.copy(p1)
p1 and p2 contain the same data, but they are not the same Point.
>>> print_point(p1)
(3, 4)
>>> print_point(p2)
(3, 4)
>>> p1 is p2
False
>>> p1 == p2
False
26 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Copying
The is operator indicates that p1 and p2 are not the same object, which is what we
expected.
But you might have expected == to yield True because these points contain the same
data.
In that case, you will be disappointed to learn that for instances, the default behavior of
the == operator is the same as the is operator; it checks object identity, not object
equivalence.
That’s because for programmer-defined types, Python doesn’t know what should be
considered equivalent. At least, not yet.
If you use copy.copy to duplicate a Rectangle, you will find that it copies the Rectangle
object but not the embedded Point.

>>> box2 = copy.copy(box)


>>> box2 is box
False
>>> box2.corner is box.corner
True

This operation is called a shallow copy because it copies the object and any references it
contains, but not the embedded point.
27 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Copying
For most applications, this is not what you want. This
behavior is confusing and error-prone.
Fortunately, the copy module provides a method
named deepcopy that copies not only the object but
also the objects it refers to, and the objects they refer
to, and so on.
You will not be surprised to learn that this operation is
called a deep copy.

box3 and box are completely separate objects


28 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
The initialization method
The initialization method is a special method that is invoked
when an object is created.
The name of this method is __init__ (two underscore
characters, followed by init, and then two more
underscores).
An initialization method for the Poitnt class looks like this:
class Point:
def __init__(self, x=0, y=0):
self.x=x
self.y=y
There is no conflict between the attribute self.x and the
parameter x.
Dot notation specifies which variable we are referring to.
29 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
The initialization method and self
The initialization method is called automatically when
the class is called.
Let’s add another method, distance_from_origin, to see
better how methods work:
>> class Point:
def __init__(self,m=0, n=0):
self.x=m
self.y=n

Creating object with initial values


>>p=Point(3,4)

30 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


class Point
>>> class Point:
def __init__(self, m=0, n=0):
self.x=m
self.y=n
def print_point(self):
print('(%g, %g)' % (self.x, self.y))

>>> p2=Point(3,4)
>>> p2.print_point()
(3, 4)
31 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
class Point
>>>class Point:
def __init__(self, x=0, y=0):
self.x=x
self.y=y
def print_point(self):
print('(%g, %g)' % (self.x, self.y))
def distace_from_origin(self):
return ((self.x**2) + (self.y**2))**0.5
>>> p5=Point(30,40)
>>> p5.print_point()
>>>p5.distace_from_origin()
32 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
__str__method
__str__ is a special method, like __init__, that is supposed
to return a string representation of an object.
If a class provides a method named __str__, it overrides
the default behavior of the Python built-in str function.
Printing a Point object implicitly invokes __str__ on the
object,
>>> C

>>> p=Point(3,4)
>>> print(p)

33 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Piont class
>>> class Point:
def __init__(self, x=0, y=0):
self.x=x
self.y=y
def __str__(self):
return'('+str(self.x)+','+str(self.y)+')'
def distace_from_origin(self):
return ((self.x**2) + (self.y**2))**0.5

>>>p=Point(3,4)
>>>p.distace_from_origin()
5.0
>>> print(p)
(3,4)
34 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Instances as parameters
You can pass an instance as a parameter to a function
in the usual way.
For example:

def print_point(self,p):
print('(%g, %g)' % (p.x, p.y))

print_point takes a point as an argument and displays it


in the standard format.
If you call print_point(p) with point p as defined
previously, the output is (3, 4).

35 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Instances as parameters

O/P

36 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Glossary

37 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Time
 As another example of a user-defined type, we’ll define a class called
Time that records the time of day.
 The class definition looks like this:
class Time:
"""Represents the time of day.
attributes: hour, minute, second
"“”
 We can create a new Time object and assign attributes for hours,
minutes, and seconds:
time = Time()
time.hour = 11
time.minute = 59
time.second = 30

38 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Pure functions
In the next few slides, we’ll write two versions of a
function called add_time, which calculates the sum of two
Times.
They will demonstrate two kinds of functions: pure
functions and modifiers.
The function creates a new Time object, initializes its
attributes, and returns a reference to the new object.
This is called a pure function because it does not modify
any of the objects passed to it as parameters
Here is an example of how to use this function. We’ll
create two Time objects: start contains the start time of a
movie, and duration contains the run time of the movie.
add_time figures out when the movie will be done.
39 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
The following is a rough version of add_time:
def add_time(t1, t2):
sum = Time()
Sum.hour = t1.hour + t2.hour
sum.minute = t1.minute + t2.minute
sum.second = t1.second + t2.second
return sum

40 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Pure functions
add_time figures out when the movie will be done.
>>> start = Time()
>>> start.hour = 9
>>> start.minute = 45
>>> start.second = 0

>>> duration = Time()


>>> duration.hour = 1
>>> duration.minute = 35
>>> duration.second = 0
>>> done = add_time(start, duration)
>>> print_time(done)
10:80:00

41 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Pure functions
class Time: start = Time()
"""Represents the time of day. start.hour = 9
attributes: hour, minute, second start.minute = 25
""“ start.second = 0
def print_time(t):
print("%d: %d: %d" % duration = Time()
(t.hour,t.minute,t.second)) duration.hour = 1
duration.minute = 35
duration.second = 0
def add_time(t1, t2):
done = add_time(start, duration)
sum = Time() print_time(done)
sum.hour = t1.hour + t2.hour
sum.minute = t1.minute +
t2.minute
sum.second = t1.second +
t2.second
return sum 42 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Pure functions
The output of this program is 10: 40: 0 which is
correct.
On the other hand, there are cases where the result is not
correct. Can you think of one?
The problem is that this function does not deal with
cases where the number of seconds or minutes adds up
to more than sixty.
When that happens, we have to carry the extra seconds
into the minutes column or the extra minutes into the
hours column.

43 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Improved version of adding time
def add_time(t1, t2):
sum = Time()
sum.hour = t1.hour + t2.hour
sum.minute = t1.minute + t2.minute
sum.second = t1.second + t2.second

if sum.second >= 60:


sum.second -= 60
sum.minute += 1

if sum.minute >= 60:


sum.minute -= 60
sum.hour += 1
return sum
44 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Improved version of adding time
class Time:
"""Represents the time of day.
start = Time()
attributes: hour, minute, second
start.hour = 9
"""
start.minute = 45
def print_time(t):
start.second = 0
print("%d: %d: %d" %
(t.hour,t.minute,t.second))
duration = Time()
def add_time(t1, t2): duration.hour = 1
sum = Time() duration.minute = 35
sum.hour = t1.hour + t2.hour duration.second = 0
sum.minute = t1.minute + t2.minute done = add_time(start, duration)
sum.second = t1.second + t2.second print_time(done)

if sum.second >= 60:


sum.second -= 60
sum.minute += 1

if sum.minute >= 60:


sum.minute -= 60
sum.hour += 1
return sum 45 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Modifiers
There are times when it is useful for a function to
modify one or more of the objects it gets as
parameters.
Usually, the caller keeps a reference to the objects it
passes, so any changes the function makes are visible
to the caller.
Functions that work this way are called modifiers.
We have defined a function called increment, which
adds a given number of seconds to a Time object.

46 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Modifiers
def increment(time, seconds):
time.second += seconds
if time.second >= 60:
time.second -= 60
time.minute += 1
if time.minute >= 60:
time.minute -= 60
time.hour += 1

47 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Improved version of adding time
class Time:
"""Represents the time of day. start = Time()
attributes: hour, minute, second start.hour = 9
""" start.minute = 45
start.second = 40
def print_time(t):
print("%d: %d: %d" % increment(start,30)
(t.hour,t.minute,t.second)) print_time(start)

def increment(time, seconds):


time.second += seconds
if time.second >= 60:
time.second -= 60
time.minute += 1
if time.minute >= 60:
time.minute -= 60
time.hour += 1 48 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Modifiers

O/P
49 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Prototype development versus planning
In the previous slides, we demonstrated an approach to program
development that we call prototype development.
The development plan I am demonstrating is called “prototype
and patch”.
For each function, I wrote a prototype that performed the
basic calculation and then tested it, patching errors along the
way.
This approach can be effective, especially if you don’t yet have a
deep understanding of the problem. But incremental
corrections can generate code that is unnecessarily complicated
—since it deals with many special cases—and unreliable—
since it is hard to know if you have found all the errors.
An alternative is planned development, in which high-level
insight into the problem can make the programming much easier.

50 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Prototype development versus planning
 In this case, the insight is that a Time object is really a
three-digit number in base 60! The second component is
the ones column, the minute component is the sixties
column, and the hour component is the thirty-six
hundreds column.
When we wrote add_time and increment, we were
effectively doing addition in base 60, which is why we had
to carry from one column to the next.
This observation suggests another approach to the whole
problem – we can convert a Time object into a single
number and take advantage of the fact that the computer
knows how to do arithmetic with numbers. The following
function converts a Time object into an integer:
51 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Prototype development versus planning
The following function converts a Time object into an
integer:

Now, all we need is a way to convert from an integer to


a Time object:
Sometimes you want both the
quotient and remainder when
dividing x by y. The function
divmod(x,y) returns a tuple (q, r),
where q is the quotient and r is
the remainder.

You can use these functions to rewrite add_time:

52 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Prototype development versus planning
class Time:
start = Time()
pass
start.hour = 9
def print_time(t): start.minute = 45
print("%d: %d: %d" %(t.hour,t.minute,t.second)) start.second = 0

def time_to_int(time): duration = Time()


duration.hour = 1
minutes = time.hour * 60 + time.minute
duration.minute = 35
seconds = minutes * 60 + time.second duration.second = 0
return seconds done = add_time(start, duration)
def int_to_time(seconds): print_time(done)
time = Time()
minutes, time.second = divmod(seconds, 60)
o/p
time.hour, time.minute = divmod(minutes, 60) 11: 20: 0
return time

def add_time(t1, t2):


seconds = time_to_int(t1) + time_to_int(t2)
return int_to_time(seconds)
53 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Prototype development versus planning

O/P 54 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


55 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Object-oriented features

Python is an object-oriented programming language, which means that it

provides features that support object-oriented programming, which has these

defining characteristics:
 Programs include class and method definitions.

 Most of the computation is expressed in terms of operations on objects.

 Objects often represent things in the real world, and methods(operations on objects) often

correspond to the ways things in the real world interact.

For example, the Time class defined already corresponds to the way people record

the time of day, and the functions we defined correspond to the kinds of things

people do with times.

Similarly, the Point and Rectangle classes defined already correspond to the

mathematical concepts of a point and a rectangle.


56 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Method
Method is a function that is associated with a particular class.

 We have seen methods for strings, lists, dictionaries and tuples.

In this chapter, we will define methods for programmer-defined

types.
Methods are semantically the same as functions, but there are two

syntactic differences:
 Methods are defined inside a class definition in order to make the relationship

between the class and the method explicit.


 The syntax for invoking a method is different from the syntax for calling a

function.

By convention, the first parameter of a method is called self,


57 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
print_time - a method inside the class definition
To make print_time a method, all we have to do is move the
function definition inside the class definition. Notice the
change in indentation.

class Time:
def print_time(self):
print("%d: %d: %d” % (self.hour,self.minute,self.second))

start = Time()
start.hour = 6
start.minute = 45
start.second = 30
start.print_time()
58 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
print_time & increament-methods inside the class time definition

class Time:
def print_time(self):
print("%d: %d: %d" % (self.hour,self.minute,self.second))
def increment(self, seconds):
self.second += seconds
if self.second >= 60:
self.second -= 60
self.minute += 1
if self.minute >= 60:
self.minute -= 60
self.hour += 1

start = Time()
start.hour = 9
start.minute = 45
start.second = 30
start.print_time()
start.increment(30)
start.print_time()
59 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
print_point, increament-methods inside the class Point definition &
find_center –method inside the class Rectangle definition

import math
class Rectangle:
class Point: def find_center(self):
def print_point(self): p = Point()
print('(%g, %g)' % (self.x, self.y)) p.x = self.corner.x + self.width/2
def distance(self): p.y = self.corner.y + self.height/2
return p
print(math.sqrt(self.x**2 + self.y**2))
box = Rectangle()
p2=Point() box.width = 100.0
p2.x=3 box.height = 200.0
box.corner = Point()
p2.y=4
box.corner.x = 0.0
p2.print_point() box.corner.y = 0.0
p2.distance() c=box.find_center()
c.print_point()

60 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


The initialization method- __init__
The initialization method is a special method that is invoked
when an object is created.
The name of this method is __init__ (two underscore
characters, followed by init, and then two more
underscores).
An initialization method for the Poitnt class looks like this:
class Point:
def __init__(self, x=0, y=0):
self.x=x
self.y=y
There is no conflict between the attribute self.x and the
parameter x.
Dot notation specifies which variable we are referring to.
61 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Using __init__ method to Point class
import math
class Point:
def __init__(self, x=0, y=0):
self.x=x
self.y=y
def print_point(self):
print('(%g, %g)' % (self.x, self.y))
def distance(self):
print(math.sqrt(self.x**2 + self.y**2))

p2=Point()
p2.print_point()
p3=Point(3,4)
p3.print_point()
p3.distance() 62 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Using __init__ method to time class
class Time:
def __init__(self, hour=0, minute=0, second=0):
self.hour = hour
self.minute = minute
self.second = second
def print_time(self):
print("%d: %d: %d" % (self.hour,self.minute,self.second))

Time1=Time()
Time1.print_time()

Time2=Time(3,45,45)
Time2.print_time()

Time3=Time(9, 45,30)
Time3.print_time()
63 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Using __init__ method to time class
class Time:
def __init__(self, hour=0, minute=0, Time1=Time()
second=0): Time1.print_time()
self.hour = hour Time1.increment(30)
self.minute = minute Time1.print_time()
self.second = second
def print_time(self): Time2=Time(3,45,45)
print("%d: %d: %d" % Time2.print_time()
(self.hour,self.minute,self.second)) Time2.increment(30)
def increment(self, seconds): Time2.print_time()
self.second += seconds
Time3=Time(9, 45,30)
if self.second >= 60:
Time3.print_time()
self.second -= 60
Time3.increment(30)
self.minute += 1 Time3.print_time()
if self.minute >= 60:
self.minute -= 60
self.hour += 1
64 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
__str__method in a class
class Time:

def __init__(self, hour=0, minute=0, second=0):


self.hour = hour
self.minute = minute
self.second = second

def __str__(self):
return '%.2d:%.2d:%.2d' % (self.hour, self.minute, self.second)

time = Time(9, 45,30)


print(time)

__str__ is a special method, like __init__, that is supposed to return a string representation of an object.

The method, __str__, returns a string representation of a Time object.

Note: print function always calls python’s built-in str function while printing

If a class provides a method named __str__, it overrides the default behaviour of the Python built-in str

function.

Printing a Time object implicitly invokes __str__ on the object, so defining __str__ also changes the behavior

of print
65 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Using __str__method in a Time class
class Time:
def __init__(self, hour=0, minute=0, Time1=Time()
second=0): print(Time1)
self.hour = hour Time1.increment(30)
print(Time1)
self.minute = minute
self.second = second Time2=Time(3,45,45)
def __str__(self): print(Time2)
Time2.increment(30)
return '%.2d:%.2d:%.2d' % (self.hour,
print(Time2)
self.minute, self.second)
Time3=Time(9, 45,30)
def increment(self, seconds): print(Time2)
self.second += seconds Time3.increment(30)
print(Time3)
if self.second >= 60:
self.second -= 60
self.minute += 1
if self.minute >= 60:
self.minute -= 60
self.hour += 1

66 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


A more complicated example-is_after
method
The is_after method is slightly more complicated because it

operates on two Time objects, not just one. We can only


convert one of the parameters to self; the other stays the
same:

def is_after(self, other):

return self.time_to_int() > other.time_to_int()


We invoke this method on one object and pass the other as

an argument:

print(time.is_after(other))
67 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
is_after method
class Time:
time = Time(11, 45,50)
def __init__(self, hour=0, minute=0, second=0): print(time)
self.hour = hour other1=Time(9, 45,50)
self.minute = minute print(other1)
self.second = second print(time.is_after(other1))

def __str__(self): O/P


return '%.2d:%.2d:%.2d' % (self.hour, self.minute,
self.second)
11: 45: 50
def time_to_int(self):
9: 45: 50
minutes = self.hour * 60 + self.minute True
seconds = minutes * 60 + self.second
return seconds

def is_after(self, other):


return self.time_to_int() > other.time_to_int()

68 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Add times
def add_time(self, other):
class Time:
seconds = self.time_to_int() +
def __init__(self, hour=0, minute=0, other.time_to_int()
second=0): return self.int_to_time(seconds)
self.hour = hour
self.minute = minute start = Time()
self.second = second start.hour = 9
start.minute = 45
def __str__(self): start.second = 0
return '%.2d:%.2d:%.2d' % (self.hour,
self.minute, self.second) duration = Time()
duration.hour = 1
def time_to_int(self):
duration.minute = 35
minutes = self.hour * 60 + self.minute
duration.second = 0
seconds = minutes * 60 + self.second
return seconds
end=start.add_time(duration)
print(start)
def int_to_time(self,seconds):
print(duration)
time = Time()
print(end)
minutes, time.second = divmod(seconds,
60)
time.hour, time.minute = divmod(minutes, 69 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Operator overloading
Some languages make it possible to change the definition of the built- in operators when they are applied
to user-defined types.
This feature is called operator overloading.
It is especially useful when defining new mathematical types.
For example, to override the addition operator +, we provide a method named __add__:
As usual, the first parameter is the object on which the method is invoked. The second parameter is
conveniently named other to distinguish it from self.
# inside class Time:
def __add__(self, other):
seconds = self.time_to_int() + other.time_to_int()
return int_to_time(seconds)

And here is how you could use it:


>>> start = Time(9, 45)
>>> duration = Time(1, 35)
>>> print(start + duration)
11:20:00

When you apply the + operator to Time objects, Python invokes __add__. When you print the result, Python
invokes __str__. So there is a lot happening behind the scenes!
Changing the behavior of an operator so that it works with programmer-defined types is called operator
overloading.
For every operator in Python there is a corresponding special method, like __add__.

70 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Operator overloading
class Time:

def __init__(self, hour=0, minute=0, second=0):


self.hour = hour
self.minute = minute
self.second = second

def __str__(self):
return '%.2d:%.2d:%.2d' % (self.hour, self.minute, self.second)

def time_to_int(self):
minutes = self.hour * 60 + self.minute
seconds = minutes * 60 + self.second
return seconds

def int_to_time(self,seconds):
time = Time()
minutes, time.second = divmod(seconds, 60)
time.hour, time.minute = divmod(minutes, 60)
return time
71 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Continued--Operator overloading
def __add__(self, other):
seconds = self.time_to_int() + other.time_to_int()
return self.int_to_time(seconds)

def __sub__(self, other):


seconds = self.time_to_int() - other.time_to_int()
return self.int_to_time(seconds)

start = Time(9,15,10)
duration = Time(9, 15,10)
print(start+duration)
print(start-duration)
72 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Operator overloading

73 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Operator Overloading

O/P
74 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Type-based dispatch
• In the previous section we added two Time objects, but you also might want to add an integer
to a Time object.
The following is a version of __add__ that checks the type of other and invokes either add_time
or increment:

def __add__(self, other):


if isinstance(other, Time):
return self.add_time(other)
else:
return self.increment(other)

def add_time(self, other):


seconds = self.time_to_int() + other.time_to_int()
return int_to_time(seconds)

def increment(self, seconds):


seconds += self.time_to_int()
return int_to_time(seconds)

The built-in function isinstance takes a value and a class object, and returns True if the value is an
instance of the class.
If other is a Time object, __add__ invokes add_time. Otherwise it assumes that the parameter is a number
and invokes increment. This operation is called a type-based dispatch because it dispatches the
computation to different methods based on the type of the arguments.

75 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Type-based dispatch
class Time:
def __init__(self, hour=0, minute=0, second=0):
self.hour = hour
self.minute = minute def add_time(self, other):
self.second = second seconds = self.time_to_int() +
other.time_to_int()
def __str__(self): return self.int_to_time(seconds)
return '%.2d:%.2d:%.2d' % (self.hour, self.minute,
self.second)
def increment(self, seconds):
def __add__(self, other): seconds += self.time_to_int()
if isinstance(other, Time): return self.int_to_time(seconds)
return self.add_time(other)
else: start = Time(9,15,10)
return self.increment(other) duration = Time(9, 15,10)
print(start+duration)
def time_to_int(self): print(start+40)
minutes = self.hour * 60 + self.minute
seconds = minutes * 60 + self.second
return seconds

def int_to_time(self,seconds):
time = Time()
minutes, time.second = divmod(seconds, 60)
time.hour, time.minute = divmod(minutes, 60)
return time 76 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Type-based dispatch
Unfortunately, this implementation of addition is not commutative. If the
integer is the first operand,
>>> print(1337 + start)
you get TypeError: unsupported operand type(s) for +: 'int' and 'instance'
The problem is, instead of asking the Time object to add an integer, Python
is asking an integer to add a Time object, and it doesn’t know how.
But there is a clever solution for this problem: the special method __radd__,
which stands for “right-side add”.
This method is invoked when a Time object appears on the right side of
the + operator. Here’s the definition:

# inside class Time:


def __radd__(self, other):
return self.__add__(other)

And here’s how it’s used:


>>> print(1337 + start)

77 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Type-based dispatch
class Time:
def __init__(self, hour=0, minute=0, second=0):
self.hour = hour
self.minute = minute def int_to_time(self,seconds):
time = Time()
self.second = second minutes, time.second = divmod(seconds, 60)
time.hour, time.minute = divmod(minutes, 60)
def __str__(self): return time
return '%.2d:%.2d:%.2d' % (self.hour, self.minute,
def add_time(self, other):
self.second)
seconds = self.time_to_int() + other.time_to_int()
return self.int_to_time(seconds) return self.int_to_time(seconds)

def __add__(self, other): def increment(self, seconds):


seconds += self.time_to_int()
if isinstance(other, Time):
return self.add_time(other) start = Time(9,15,10)
else: duration = Time(9, 15,10)
print(start+duration)
return self.increment(other)
print(start+30)
print(30+start)
def __radd__(self, other):
return self.__add__(other)

def time_to_int(self):
minutes = self.hour * 60 + self.minute
seconds = minutes * 60 + self.second
return seconds

78 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Polymorphism
Most of the methods we have written only work for a
specific type.
When you create a new object, you write methods that
operate on that type.
But there are certain operations that you will want to
apply to many types, such as the arithmetic operations
If many types support the same set of operations, you
can write functions that work on any of those types.
Functions that work with several types are called
polymorphic.

79 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Polymorphism
Type-based dispatch is useful when it is necessary, but (fortunately) it is not always
necessary.
Often you can avoid it by writing functions that work correctly for arguments with different
types.
Many of the functions we wrote for strings also work for other sequence types. For example,
we can write a function known as histogram to count the number of times each letter
appears in a word.

def histogram(s):
d = dict()
for c in s:
if c not in d:
d[c] = 1
else:
d[c] = d[c]+1
return d

This function works for strigns, and also works for lists, tuples, and even dictionaries,
as long as the elements of s are hashable, so they can be used as keys in d.
Functions that work with several types are called polymorphic.
 Polymorphism can facilitate code reuse.

80 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Polymorphism
def histogram(s):
d = dict()
for c in s:
if c not in d:
d[c] = 1
else:
d[c] = d[c]+1
return d

s1=“engineering"
x=histogram(s1)
print(x)

l = ['spam', 'egg', 'spam', 'spam', 'bacon', 'spam']


x1=histogram(l)
print(x1)

T1 = ("Apple", "Banana", "Orange","Apple")


x2=histogram(T1)
print(x2)

Days = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Sunday"}


x3=histogram(Days)
print(x3)

Employee = {"Name": "John", "Age": 29, "salary":25000,"Company":"GOOGLE"}


x4=histogram(Employee)
print(x4)

81 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Polymorphism

O/P
82 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Interface and implementation

One of the goals of object-oriented design is to make software more maintainable, which means
that you can keep the program working when other parts of the system change, and modify the
program to meet new requirements.
A design principle that helps achieve that goal is to keep interfaces separate from
implementations.
For objects, that means that the methods a class provides should not depend on how the
attributes are represented.
For example, in this chapter we developed a class that represents a time of day. Methods provided
by this class include time_to_int, is_after, and add_time.
We could implement those methods in several ways. The details of the implementation depend
on how we represent time.
In this chapter, the attributes of a Time object are hour, minute, and second. As an alternative,
we could replace these attributes with a single integer representing the number of seconds since
midnight. This implementation would make some methods, like is_after, easier to write, but it
makes other methods harder.
After you deploy a new class, you might discover a better implementation. If other parts of the
program are using your class, it might be time-consuming and error-prone to change the interface.
But if you designed the interface carefully, you can change the implementation without

changing the interface, which means that other parts of the program don’t have to change.

83 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Interface and implementation
At a high level (statically typed languages) an interface
acts as a blueprint for designing classes.
Like classes, interfaces define methods.
Unlike classes, these methods are abstract.
An abstract method is one that the interface simply
defines. It doesn’t implement the methods. This is done
by classes, which then implement the interface and
give concrete meaning to the interface’s abstract
methods.

84 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Interface and implementation
Interfaces are concepts that belong to statically typed
languages such as Java or C#, and do not really apply
to dynamic language such as Python.
python does not have any equivalent of interfaces .
 Since Python does support multiple inheritance, you can
easily emulate the equivalence of interfaces.
What that means is that interfaces are implicit in Python : if
an object conforms to an interface, you can use it, no need
to declare it like you would in statically typed languages such
as Java or C# .
There's no interface keyword in Python. The Java / C# way
of using interfaces is not available here. In the dynamic
language world, things are more implicit. We're more focused
on how an object behaves, rather than it's type/class.
85 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Inheritance
Inheritance is the ability to define a new class that is a modified version of an
existing class.
In this chapter I demonstrate inheritance using classes that represent playing
cards, decks of cards, and poker hands.

• There are 52 cards


• Cards are grouped into 4 suits named as Club, Spread, Diamond and Hearts
• Club &spread are black suits and diamond &hearts are red suits
• Each suit consists of 13 cards names as Ace, 2, 3, 4, 5, 6, 7, 8, 9, 10, Jack, Queen and
King.
• Jack,queen And King are called picture cards.
• Depending on the game that you are playing,
86
an Ace may be higher©than King or lower
Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Inheritance
 If we want to define a new object to represent a playing card, it is obvious what the attributes should be:
rank and suit. We use integers to encode the ranks and suits.
 In this context, “encode”means that we are going to define a mapping between numbers and suits, or
between numbers and ranks.
 For example, this table shows the suits and the corresponding integer codes:
Spades  3
Hearts  2
Diamonds  1
Clubs  0
 This code makes it easy to compare cards; because higher suits map to higher numbers, we can compare
suits by comparing their codes.
 The mapping for ranks is fairly obvious; each of the numerical ranks maps to the corresponding integer, and
for face cards:
Jack  11
Queen  12
King  13
 The class definition for Card looks like this:

class Card:
def __init__(self, suit=0, rank=0):
self.suit = suit
self.rank = rank
 To create a Card, you call Card with the suit and rank of the card you want.

queen_of_diamonds = Card(1, 12) 87 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Class attributes
 In order to print Card objects in a way that people can easily read, we need a mapping
from the integer codes to the corresponding ranks and suits.
 A natural way to do that is with lists of strings. We assign these lists to class
attributes:

# inside class Card:


suit_names = ['Clubs', 'Diamonds', 'Hearts', 'Spades']
rank_names = [None, 'Ace', '2', '3', '4', '5', '6', '7','8', '9', '10', 'Jack', 'Queen', 'King']
def __str__(self):
return '%s of %s' % (Card.rank_names[self.rank], Card.suit_names[self.suit])

 Variables like suit_names and rank_names, which are defined inside a class but
outside of any method, are called class attributes because they are associated with
the class object Card.
 This term distinguishes them from variables like suit and rank, which are called
instance attributes because they are associated with a particular instance.
88 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Class attributes

The above figure indicates a diagram of the Card class object and one Card instance.
• Card is a class object; its type is type. card1 is an instance of Card, so its type is Card.
• Every card has its own suit and rank, but there is only one copy of suit_names and
rank_names.
• Putting it all together, the expression Card.rank_names[self.rank] means “use the attribute
rank from the object self as an index into the list rank_names from the class Card, and
select the appropriate string.”
• The first element of rank_names is None because there is no card with rank zero. By
including
None as a place-keeper, we get a mapping with the nice property that the index 2 maps to the
string '2', and so on. With the methods we have so far, we can create and print cards:
>>> card1 = Card(2, 11) 89 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
>>> print(card1)
Class attributes
class Card:
def __init__(self, suit=0, rank=2):
self.suit = suit
self.rank = rank

suit_names = ['Clubs', 'Diamonds', 'Hearts', 'Spades']


rank_names = [None, 'Ace', '2', '3', '4', '5', '6', '7','8', '9', '10', 'Jack', 'Queen', 'King']
def __str__(self):
return '%s of %s' % (Card.rank_names[self.rank],Card.suit_names[self.suit])

card1 = Card(2, 11)


print(card1)

O/P
Jack of Hearts

90 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Class attributes
class Card:
"""Represents a standard playing card."""
def __init__(self, suit=0, rank=2):
self.suit = suit
self.rank = rank

suit_names = ['Clubs', 'Diamonds', 'Hearts', 'Spades']


rank_names = [None, 'Ace', '2', '3', '4', '5', '6', '7','8', '9', '10', 'Jack', 'Queen', 'King']

def __str__(self):
return '%s of %s' % (Card.rank_names[self.rank],Card.suit_names[self.suit])

queen_of_diamonds = Card(1, 12)


king_of_diamonds = Card(1, 13)
card1 = Card(2, 11)
print(queen_of_diamonds)
print(king_of_diamonds)
print(card1) 91 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Comparing cards
For built-in types, there are relational operators (<, >, ==, etc.) that compare
values and determine when one is greater than, less than, or equal to another.
 For programmer-defined types, we can override the behavior of the built-in
operators by providing a method named __lt__, which stands for “less than”.
__lt__ takes two parameters, self and other, and returns True if self is strictly
less than other.
The correct ordering for cards is not obvious. For example, which is better, the
3 of Clubs or the 2 of Diamonds? One has a higher rank, but the other has a
higher suit.
In order to compare cards, you have to decide whether rank or suit is more
important.
The answer might depend on what game you are playing, but to keep things
simple, we’ll make the arbitrary choice that suit is more important, so all of
the Spades outrank all of the Diamonds, and so on.

92 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Overriding the operator < using
__lt__: function

# inside class Card:


def __lt__(self, other):
# check the suits
if self.suit < other.suit:
return True
if self.suit > other.suit:
return False
# suits are the same... check ranks
return self.rank < other.rank

93 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Comparing cards
class Card:
def __init__(self, suit=0, rank=2):
self.suit = suit
self.rank = rank
suit_names = ['Clubs', 'Diamonds', 'Hearts', 'Spades']
rank_names = [None, 'Ace', '2', '3', '4', '5', '6', '7','8', '9', '10', 'Jack', 'Queen', 'King']
def __str__(self):
return '%s of %s' % (Card.rank_names[self.rank],Card.suit_names[self.suit])
def __lt__(self, other):
if self.suit < other.suit: return True
if self.suit > other.suit: return False
# suits are the same... check ranks
return self.rank < other.rank
card1= Card(1, 12)
card2= Card(1, 13)
card3 = Card(2, 11)
print(card1)
print(card2)
print(card3)
print(card1 < card2)

94 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


tuple comparison
We can write __lt__ method more concisely using tuple
comparison
def __lt__(self, other):
t1 = self.suit, self.rank
t2 = other.suit, other.rank
return t1 < t2
Tuples are compared position by position:
 the first item of the first tuple is compared to the first item of
the second tuple; if they are not equal (i.e. the first is greater or
smaller than the second) then that's the result of the
comparison,
 else the second item is considered, then the third and so on.'''

95 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


tuple comparison
class Card:
def __init__(self, suit=0, rank=0):
self.suit = suit
self.rank = rank

suit_names = ['Clubs', 'Diamonds', 'Hearts', 'Spades']


rank_names = [None, 'Ace', '2', '3', '4', '5', '6', '7','8', '9', '10', 'Jack', 'Queen', 'King']

def __str__(self):
return '%s of %s' % (Card.rank_names[self.rank],Card.suit_names[self.suit])

def __lt__(self, other):


t1 = self.suit, self.rank
t2 = other.suit, other.rank
return t1 < t2

card1= Card(1, 12)


card2= Card(1, 13)
card3 = Card(2, 11)

print(card1)
print(card2)
print(card3)
print(card1<card2)
print(card1<card3) 96 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Decks
Now that we have Cards, the next step is to define Decks. Since a deck is made up of
cards, it is natural for each Deck to contain a list of cards as an attribute.
The following is a class definition for Deck. The init method creates the attribute cards and
generates the standard set of fifty-two cards:

class Deck:
def __init__(self):
self.cards = []
for suit in range(4):
for rank in range(1, 14):
card = Card(suit, rank)
self.cards.append(card)

• The easiest way to populate the deck is with a nested loop. The outer loop enumerates the
suits from 0 to 3. The inner loop enumerates the ranks from 1 to 13. Each iteration creates
a new Card with the current suit and rank, and appends it to self.cards.

97 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Printing the deck

Here is a __str__ method for Deck:


#inside class Deck:
def __str__(self):
res = []
for card in self.cards:
res.append(str(card))
return '\n'.join(res)

This method demonstrates an efficient way to accumulate a large


string: building a list of strings and then using the string method
join.
The built-in function str invokes the __str__ method on each card
and returns the string representation.
Since we invoke join on a newline character, the cards are
separated by newlines.
98 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Printing the deck
>>> deck = Deck()
>>> print(deck)
Ace of Clubs
2 of Clubs
3 of Clubs
...
10 of Spades
Jack of Spades
Queen of Spades
King of Spades
Even though the result appears on 52 lines, it is one long
string that contains newlines.
99 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
import random
class Card:
def __init__(self, suit=0, rank=2):
self.suit = suit
self.rank = rank
suit_names = ['Clubs', 'Diamonds', 'Hearts', 'Spades']
rank_names = [None, 'Ace', '2', '3', '4', '5', '6', '7','8', '9', '10', 'Jack', 'Queen', 'King']
def __str__(self):
return '%s of %s' % (Card.rank_names[self.rank],Card.suit_names[self.suit])
class Deck:
def __init__(self):
self.cards = []
for suit in range(4):
for rank in range(1, 14):
card = Card(suit, rank)
self.cards.append(card)
def __str__(self):
res = []
for card in self.cards:
res.append(str(card))
return '\n'.join(res)

deck = Deck()
print(deck)

100 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Add, remove, shuffle
Pop()pop() is an inbuilt function in Python that removes and returns last value from the list or
the given index value. Parameter : index (optional) - The value at index is popped out and
removed. If the index is not given, then the last element is popped out and removed

To deal cards, we would like a method that removes a card from the deck and returns it.
The list method pop provides a convenient way to do that:
def pop_card(self):
return self.cards.pop()

Since pop removes the last card in the list, we are dealing from the bottom of the deck.

To add a card, we can use the list method append:


def add_card(self, card):
self.cards.append(card)

As another example, we can write a Deck method named shuffle using the function shuffle
from the random module:
def shuffle(self):
random.shuffle(self.cards)

101 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Printing the deck
import random
class Card:
def __init__(self, suit=0, rank=2):
self.suit = suit
self.rank = rank
suit_names = ['Clubs', 'Diamonds', 'Hearts', 'Spades']
rank_names = [None, 'Ace', '2', '3', '4', '5', '6', '7','8', '9', '10', 'Jack', 'Queen', 'King']
def __str__(self):
return '%s of %s' % (Card.rank_names[self.rank],Card.suit_names[self.suit])

class Deck:
def __init__(self):
self.cards = []
for suit in range(4):
for rank in range(1, 14):
card = Card(suit, rank)
self.cards.append(card)

102 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Printing the deck
def __str__(self): card2=Card(1,12)
res = [] deck = Deck()
for card in self.cards: print(deck)
res.append(str(card)) print(deck.pop_card())
return '\n'.join(res) deck.add_card(card2)
print(deck.pop_card())
def pop_card(self):
return self.cards.pop() deck.shuffle()
print(deck.pop_card())
def add_card(self, card): deck.shuffle()
self.cards.append(card) print(deck.pop_card())

def shuffle(self):
random.shuffle(self.cards)
103 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Inheritance
Inheritance is the ability to define a new class that is a modified version of an existing class
As an example, let’s say we want a class to represent a “hand”, that is, the cards held by one player.
A hand is similar to a deck: both are made up of a collection of cards, and both require operations like
adding and removing cards.
A hand is also different from a deck; there are operations we want for hands that don’t make sense for a
deck. For example, in poker we might compare two hands to see which one wins. In bridge, we might
compute a score for a hand in order to make a bid.
 To define a new class that inherits from an existing class, you put the name of the existing

class in parentheses:

class Hand(Deck):
"""Represents a hand of playing cards.""“

This definition indicates that Hand inherits from Deck; that means we can use methods like

Pop_card and add_card for Hands as well as Decks.


When a new class inherits from an existing one, the existing one is called the parent and the new class is called
the child.
In this example, Hand inherits __init__ from Deck, but it doesn’t really do what we want: instead of populating
the hand with 52 new cards, the init method for Hands should initialize
cards with an empty list.

104 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Inheritance
If we provide an init method in the Hand class, it overrides the one in the Deck class:
# inside class Hand:
def __init__(self, label=''):
self.cards = []
self.label = label
When you create a Hand, Python invokes this init method, not the one in Deck.
>>> hand = Hand('new hand')
>>> hand.cards
[]
>>> hand.label
'new hand‘

The other methods are inherited from Deck, so we can use pop_card and add_card to
deal
a card:
>>> deck = Deck()
>>> card = deck.pop_card()
>>> hand.add_card(card)
>>> print(hand)
King of Spades 105 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Inheritance
import random
class Card:
def __init__(self, suit=0, rank=2):
self.suit = suit
self.rank = rank
suit_names = ['Clubs', 'Diamonds', 'Hearts', 'Spades']
rank_names = [None, 'Ace', '2', '3', '4', '5', '6', '7','8', '9', '10', 'Jack', 'Queen', 'King']

def __str__(self):
return '%s of %s' % (Card.rank_names[self.rank],Card.suit_names[self.suit])

class Deck:
def __init__(self):
self.cards = []
for suit in range(4):
for rank in range(1, 14):
card = Card(suit, rank)
self.cards.append(card)

106 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT


Inheritance
def __str__(self): hand1 = Hand('new hand')
res = [] print(hand1.cards)
for card in self.cards:
print(hand1.label)
res.append(str(card))
deck1 = Deck()
return '\n'.join(res)
card1 = deck1.pop_card()
def pop_card(self): hand1.add_card(card1)
return self.cards.pop() print(hand1)
card2 = deck1.pop_card()
def add_card(self, card): hand1.add_card(card2)
self.cards.append(card)
print(hand1)
def shuffle(self):
random.shuffle(self.cards)
O/P
class Hand(Deck):
def __init__(self, label=''): []
self.cards = [] new hand
self.label = label
King of Spades
def move_cards(self, hand, num):
for i in range(num):
King of Spades
hand.add_card(self.pop_card()) Queen of Spades
107 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
move_cards
A natural next step is to encapsulate this code in a
method called move_cards:

#inside class Deck:


def move_cards(self, hand, num):
for i in range(num):
hand.add_card(self.pop_card())

move_cards takes two arguments, a Hand object and the


number of cards to deal.
It modifies both self and hand, and returns None.
In some games, cards are moved from one hand to
another, or from a hand back to the deck.
108 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
move_cards
class Hand(Deck):
def __init__(self, label=''):
self.cards = []
self.label = label
O/P
def move_cards(self, hand, num): []
for i in range(num): new hand
hand.add_card(self.pop_card())
Jack of Spades
hand1 = Hand('new hand') Queen of Spades
print(hand1.cards) King of Spades
print(hand1.label) >>>
deck1 = Deck()
card1 = deck1.pop_card()
card2 = deck1.pop_card()
card3=deck1.pop_card()
hand1.add_card(card1)
hand1.add_card(card2)
hand1.add_card(card3)
hand2 = Hand('new hand2')
hand1.move_cards(hand2,3)
print(hand2) 109 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Inheritance pros & cons
Inheritance is a useful feature.
Some programs that would be repetitive without inheritance can
be written more elegantly with it.
 Inheritance can facilitate code reuse, since you can
customize the behavior of parent classes without having to
modify them
In some cases,the inheritance structure reflects the natural
structure of the problem, which makes the design easier to
understand.
On the other hand, inheritance can make programs difficult to
read.
When a method is invoked, it is sometimes not clear where to
find its definition.
 The relevant code may be spread across several modules
110 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Class diagrams
 A class diagram is a more abstract representation of the structure of a program.
Instead of showing individual objects,it shows classes and the relationships
between them.
 There are several kinds of relationship between classes:
 Objects in one class might contain references to objects in another class.
For example, each Rectangle contains a reference to a Point, and each Deck
contains references to many Cards. This kind of relationship is called HAS-A,
as in, “a Rectangle has a Point.”
 One class might inherit from another. This relationship is called IS-A, as in, “a
Hand is a kind of a Deck.”
 One class might depend on another in the sense that objects in one class take
objects in the second class as parameters, or use objects in the second class as
part of a computation. This kind of relationship is called a dependency.
A class diagram is a graphical representation of these relationships. For example,
Figureshows the relationships between Card, Deck and Hand .
111 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT
Class diagrams

• The arrow with a hollow triangle head represents an IS-A relationship; in this case it
indicates that Hand inherits from Deck.
• The standard arrow head represents a HAS-A relationship; in this case a Deck has
references to Card objects.
• The star (*) near the arrow head is a multiplicity; it indicates how many Cards a
Deck has. A multiplicity can be a simple number, like 52, a range, like
• There are no dependencies in this diagram. They would normally be shown with a
dashed arrow. Or if there are a lot of dependencies, they are sometimes omitted.

112 © Dr.S.Gowrishankar, Dept. of CSE, Dr.AIT

You might also like