Module-5: Classes, objects and methods
Module-5: Classes, objects and methods
Prepared by: Dr. B Latha Shankar, Associate Professor, IEM Department, SIT Tumakuru
Syllabus: Classes and objects: Programmer-defined types, Attributes, Rectangles, Instances as return values,
Objects are mutable, Copying.
Classes and functions: Time, Pure functions, Modifiers, Prototyping versus planning,
Classes and methods: Object-oriented features, Printing objects, Another example, A more complicated
example, The __init__ method, The str method, Operator overloading, Type-based dispatch, Polymorphism,
Interface and implementation.
Textbook 2: Chapters 15 – 17 08 hrs.
Classes and objects
Python is an object oriented programming language.
Almost everything in Python is an object, with its properties and methods.
Classes in Python:
class is a user-defined data type that contains both the data and the methods that are used to
manipulate it.
class is an object constructor, or a "blueprint" for creating objects. It serves as a template to create
objects. It provides the characteristics and operations that the objects will use.
Ex: Consider a prototype of a building. A building contains all the details about the floor, rooms, doors,
windows, etc. We can make as many buildings as we want, based on these details. Hence, the
prototype of building can be seen as a class, and we can create as many objects of this class.
Creating Classes:
class is created by using the keyword class, followed by the class name and colon.
Syntax:
class ClassName:
#statements
Ex:
class Point:
"""Represents a point in 2-D space.
Attributes: coordinates x and y. """
The header indicates that the new class is called Point. The body is a docstring that explains what the
class is for. You can define variables and methods inside a class. (What is docstring? It provides a
convenient way of associating documentation with Python modules, functions, classes, and method)
Objects in Python:
An object is a particular instance of a class with unique characteristics and functions.
By using the class constructor, you can create an object of a class. The object's attributes are initialized in
the class constructor, which is a special procedure with the name __init__.
The class object is like a factory for creating objects.
Syntax to create an object:
object_name = Class_Name (arguments)
Ex: >>> blank = Point()
5-1
Module-5: Classes, objects and methods
Here blank is an object and Point is a class.
Creating a new object is called instantiation, and the object is an instance of the class. So “object”
and “instance” are interchangeable.
Attributes:
You can assign values to an instance using dot notation:
>>> blank.x = 3.0
>>> blank.y = 4.0
Here, values are assigned to elements of an object. These elements are called attributes.
A state diagram that shows an object and its attributes is called an object diagram and shown in
Figure 5-1.
The variable blank refers to a Point object, which contains two
attributes.
Each attribute refers to a floating-point number.
Figure 5-1. Object diagram.
Defining a class to represent rectangles:
class Rectangle:
"""Represents a rectangle.
attributes: width, height, corner.
“""
The docstring lists the attributes: width and height are numbers; corner is a Point object that
specifies the lower-left corner.
To represent a rectangle, instantiate (create) 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
Figure 5-2 shows the state of this object.
Figure 5-2. Object diagram.
Instances as Return Values
Functions can return instances.
Ex1: find_center takes a Rectangle object called rect as parameter and returns a Point that
contains the coordinates of the center of the rect:
def find_center(rect):
p = Point() # creating an object
p.x = rect.corner.x + rect.width/2 # assigning values to the attributes
p.y = rect.corner.y + rect.height/2 # assigning values to the attributes
return p
5-2
Module-5: Classes, objects and methods
find_center function passes box as an argument and assigns the resulting Point to center:
>>> center = find_center(box)
>>> print(center)
(50, 100)
Objects Are Mutable
State of an object can be changed by making an assignment to one of its attributes.
Ex: to change the size of a rectangle: modify its width and height:
box.width = box.width + 50
box.height = box.height + 100
Functions also can be used to modify objects.
Ex: grow_rectangle function 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
Present width and height are:
>>> box.width, box.height
(150.0, 300.0)
>>> grow_rectangle(box, 50, 100) #using function to modify the objects
>>> box.width, box.height
(200.0, 400.0)
• Inside the function, rect is an alias for box, so when the function modifies rect, box also
changes.
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.
Copying an object is often an alternative to aliasing. The copy module contains a function called
copy that can duplicate any object:
>>> p1 = Point() # creating an object
>>> p1.x = 3.0 # assigning values to the attributes
>>> p1.y = 4.0 # assigning values to the attributes
>>> import copy # importing copy module
>>> p2 = copy.copy(p1) # Getting a copy of p1 and naming it as p2
• p1 and p2 contain the same data, but they are not the same Point:
>>> print_point(p1) # print_time is a function that takes a Time object and prints it in the
#hour : minute : second.
(3, 4)
>>> print_point(p2)
(3, 4)
>>> p1 == p2
False
The ‘==’ operator checks object identity, not object equivalence and indicates that p1 and p2
are not the same objects.
If copy.copy is used to duplicate a Rectangle, it copies the Rectangle object but not the
embedded Point:
>>> box2 = copy.copy(box)
5-3
Module-5: Classes, objects and methods
>>> box2 is box # box2 and box are completely separate objects.
False
>>> box2.corner is box.corner # box2 and box have same embedded point
True
This operation is called a shallow copy because it copies the object and any references it
contains, but not the embedded objects.
Figure 5-3. Object diagram.
Figure 5-3 shows the corresponding object diagram.
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. Hence the name deepcopy.
Ex:
>>> box3 = copy.deepcopy(box)
>>> box3 is box
False # box3 and box are completely separate objects.
>>> box3.corner is box.corner
False # box3 & box have separate embedded points.
Debugging
Following is the exception (error) which you get when you are working with objects:
If you try to access an attribute that doesn’t exist, you get an AttributeError:
>>> p = Point()
>>> p.x = 3
>>> p.y = 4
>>> p.z
AttributeError: Point instance has no attribute 'z'
If you are not sure what type an object is, you can get to know about type using:
>>> type(p)
<class '__main__.Point'>
use isinstance to check whether an object is an instance of a class:
>>> isinstance(p, Point) # p is an object and Point is a class
True
If you are not sure whether an object has a particular attribute, use the built-in function
hasattr:
>>> hasattr(p, 'x') # p is an object and ‘x’ is an attribute expressed as a string
True
>>> hasattr(p, 'z') # p is an object and ‘z’ is an attribute expressed as a string
False
Here, the first argument can be any object; the second argument is a string that contains the
name of the attribute.
You can also use a try and except clause to see if the object has the attributes you need:
try:
x = p.x
except AttributeError:
5-4
Module-5: Classes, objects and methods
x = 0
Classes and functions
Time
Consider an example of a programmer-defined type, called class Time that records the time of day:
class Time:
"""Represents the time of day.
attributes: hour, minute, second
"""
Create a new Time object and assign attributes for hours, minutes, and seconds:
time = Time() # creating an object
time.hour = 11 # initializes its attributes
time.minute = 59 # initializes its attributes
time.second = 30 # initializes its attributes
The state diagram for the Time object is given in Figure 5-4.
Figure 5-4. Object diagram.
Pure Functions
Consider a function that adds time values:
def add_time(t1, t2):
sum = Time() # creating a Time object
sum.hour = t1.hour + t2.hour # initializes its attributes
sum.minute = t1.minute + t2.minute # initializes its attributes
sum.second = t1.second + t2.second # initializes its attributes
return sum # Returns a reference to new object
• 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 objects
passed to it as arguments & it has no effect, like displaying a value or getting user input, other
than returning a value.
• To test this function, consider two Time objects: start contains the start time of a movie, like
Monty Python and the Holy Grail, and duration contains the runtime of the movie, which is 1
hour 35 minutes. add_time finds out when the movie will be done:
>>> start = Time() # creating an object
>>> start.hour = 9
>>> start.minute = 45
>>> start.second = 0
>>> duration = Time() # creating another object
>>> duration.hour = 1
>>> duration.minute = 35
>>> duration.second = 0
>>> done = add_time(start, duration)
>>> print_time(done) # print_time is a function that takes a Time object and prints it in the form
#hour : minute : second.
10:80:00
5-5
Module-5: Classes, objects and methods
• The result, 10:80:00, has a problem. This function does not deal with cases where the number of
seconds or minutes are more than 60. When that happens, we have to “carry” the extra seconds
into the minute column or the extra minutes into the hour column.
• Improved version:
def add_time(t1, t2):
sum = Time() # creating an object
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
Modifiers
• Sometimes it is useful for a function to modify the objects it gets as parameters. In that case,
the changes are visible to the caller. Functions that work this way are called modifiers.
• increment, which adds a given number of seconds to a Time object, can be written naturally
as a modifier, as shown:
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
• Anything that can be done with modifiers can also be done with pure functions. Programs
that use pure functions are faster to develop and less error-prone than programs that use
modifiers. But modifiers are convenient at times, and functional programs tend to be less
efficient.
• Use functional programming style which uses pure functions whenever it is reasonable
and resort to modifiers only if there is a compelling advantage.
Prototyping versus Planning
• When a problem statement is not known completely, we may write the program initially, and then
keep modifying it as and when requirement (problem definition) changes. This methodology is
known as prototype and patch.
• That is, first design the prototype based on the information available and then perform patch-
work as and when extra information is gathered.
• An alternative is designed development, in which high-level insight into the problem can make
the programming much easier.
• Ex: In add_time and increment methods, 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 Time objects to integers and take advantage of the fact
that the computer knows how to do integer arithmetic.
5-6
Module-5: Classes, objects and methods
Classes and methods
Python is an object-oriented programming language, which means that it provides features that support
object-oriented programming. It has following 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 often correspond to the ways
things in the real world interact.
Ex:
Time class (defined in later in this chapter) corresponds to the way people record the time of day,
and the methods defined correspond to the kinds of things people do with times.
Methods
A method is a function that is associated with a particular class.
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.
Printing Objects
• Define a class named Time and a function named print_time:
class Time:
"""Represents the time of day."""
def print_time(time):
print('%.2d:%.2d:%.2d' % (time.hour, time.minute, time.second))
• To call this function, pass a Time object as an argument:
>>> start = Time() # creating a Time object
>>> start.hour = 9 # assigning values to the attributes
>>> start.minute = 45 # assigning values to the attributes
>>> start.second = 00 # assigning values to the attributes
>>> print_time(start) # passing a Time object as an argument to function print_time
09:45:00
• To make print_time a method, move the function definition inside the class definition.
Notice the change in indentation. '%.2d' prints an integer using at least two digits.
class Time:
def print_time(time): # Notice the change in indentation.
print('%.2d:%.2d:%.2d' % (time.hour, time.minute, time.second))
• There are two ways to call print_time.
1. The first (and less common) way is to use function syntax:
>>> Time.print_time(start)
09:45:00
In this use of dot notation, Time is the name of the class, and print_time is the name of the method. start
is passed as a parameter.
2. The second (and more concise) way is to use method syntax:
>>> start.print_time()
09:45:00
5-7
Module-5: Classes, objects and methods
In this use of dot notation, print_time is the name of the method, and start is the object the method
is invoked on, which is called the subject. Just as the subject of a sentence is what the sentence is
about, the subject of a method invocation is what the method is about.
• Inside the method, the subject is assigned to the first parameter, so in this case start is assigned
to time.
• By convention, the first parameter of a method is called self, so it would be more common to
write print_time like this:
class Time:
def print_time(self):
print('%.2d:%.2d:%.2d' % (self.hour, self.minute, self.second))
• self variable: It is like a placeholder for the object created
Another Example
class Time:
def increment(self, seconds):
seconds += self.time_to_int()
return int_to_time(seconds)
Here the definition of functions used:
def time_to_int(time): # a function that converts a Time to an integer.
minutes = time.hour * 60 + time.minute
seconds = minutes * 60 + time.second
return seconds
def int_to_time(seconds): # a function that converts an integer to a Time.
time = Time() # creating an object
minutes, time.second = divmod(seconds, 60)
time.hour, time.minute = divmod(minutes, 60)
return time
(# Note: The built-in function divmod takes two arguments and returns a tuple of two values, the
quotient and remainder:
>>> t = divmod(7, 3)
>>> quot, rem = divmod(7, 3)
>>> quot
2
>>> rem
1 Note ended)
Invoke increment as:
>>> start.print_time()
09:45:00
>>> end = start.increment(1337)
>>> end.print_time()
10:07:17
The subject(= object), start, gets assigned to the first parameter, self of the method
increment. The argument, 1337, gets assigned to the second parameter, seconds.
• If you invoke increment with two arguments, you get:
>>> end = start.increment(1337, 460) ….(1)
TypeError: increment() takes 2 positional arguments but 3 were given
• The error message is initially confusing, because the method increment has 2 parameters in its
definition, and hence it seems that it can take 2 arguments. In (1) there are only two arguments
5-8
Module-5: Classes, objects and methods
in parentheses. But the subject is also considered an argument, so all together that’s three.
(# Note: a positional argument is an argument that doesn’t have a parameter name; that is, it is not a
keyword argument. Ex. In this function call:
sketch(parrot, cage, dead=True)
Here parrot and cage are positional, and dead is a keyword argument.)
A More Complicated Example
• is_after method it takes two Time objects as parameters. In this case it is conventional to
name the first parameter self and the second parameter other:
# inside class Time:
def is_after(self, other):
return self.time_to_int() > other.time_to_int()
• To use this method, invoke it on one object and pass the other as an argument:
>>> end.is_after(start)
True
• This syntax is almost reads like English: “end is after start?”
The __init__ Method
• The __init__ method (short for “initialization”) is a special (magic) method that gets invoked
when an object is instantiated. Its full name is __init__ (two underscore characters, followed
by init, and then two more underscores).
• An init method for the Time class:
# inside class Time:
def __init__(self, hour=0, minute=0, second=0):
self.hour = hour
self.minute = minute
self.second = second
• It is common for the parameters of __init__ to have the same names as the attributes. The
statement self.hour = hour stores the value of the parameter hour as an attribute of
self.
• The parameters are optional, so if you call Time with no arguments, you get the default
values:
>>> time = Time()
>>> time.print_time()
00:00:00
• If you provide one argument, it overrides hour:
>>> time = Time (9)
>>> time.print_time()
09:00:00
• If you provide two arguments, they override hour and minute:
>>> time = Time(9, 45)
>>> time.print_time()
09:45:00
• And if you provide three arguments, they override all three default values.
The __str__ Method
• __str__ is a special method, like __init__, that is supposed to return a string
representation of an object.
5-9
Module-5: Classes, objects and methods
• Ex: here is a str method for Time objects:
# inside class Time:
def __str__(self):
return '%.2d:%.2d:%.2d' % (self.hour, self.minute, self.second)
When you print an object, Python invokes the str method:
>>> time = Time(9, 45)
>>> print(time)
09:45:00
__init__ method which makes it easier to instantiate objects, and __str__, which is useful for
debugging.
Operator Overloading
• By defining other special methods, it is possible to specify the behavior of operators on
programmer-defined types.
• Ex: if you define a method named __add__ for the Time class, you can use the + operator on
Time objects:
# inside class Time:
def __add__(self, other):
seconds = self.time_to_int() + other.time_to_int()
return int_to_time(seconds)
To 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__.
• 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__.
Type-Based Dispatch
• In the previous section two Time objects were added, but how 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:
# inside class Time:
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()
5-10
Module-5: Classes, objects and methods
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.
Examples that use the + operator with different types:
>>> start = Time(9, 45)
>>> duration = Time(1, 35)
>>> print(start + duration)
11:20:00
>>> print(start + 1337)
10:07:17
If the integer is the first operand,
>>> print(1337 + start)
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.
• To solve this use: 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:
# inside class Time:
def __radd__(self, other):
return self.__add__(other)
And it’s used as:
>>> print(1337 + start)
10:07:17
Polymorphism
• Type-based dispatch can be avoided by writing functions that work correctly for arguments
with different types.
• Many of the functions written for strings also work for other sequence types.
• Ex: Consider a function 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 also works for lists, tuples, and even dictionaries, as long as the elements of s are
immutable, so they can be used as keys in d:
>>> t = ['spam', 'egg', 'spam', 'spam', 'bacon', 'spam']
>>> histogram(t)
{'bacon': 1, 'egg': 1, 'spam': 4}
• Functions that work with several types are called polymorphic. Polymorphism can facilitate code
reuse.
5-11
Module-5: Classes, objects and methods
• Ex:, the built-in function sum, which adds the elements of a sequence, works as long as the
elements of the sequence support addition.
• Since Time objects provide an add method, they work with sum:
>>> t1 = Time(7, 43)
>>> t2 = Time(7, 41)
>>> t3 = Time(7, 37)
>>> total = sum([t1, t2, t3])
>>> print(total)
23:01:00
Interface and Implementation
• In object oriented programs classes are the interface and how the object is processed and
executed is the implementation.
• The difference between interface and implementation is: Interface: It defines what an object
can do but won’t actually do. Implementation: It actually carries out the instructions defined in
Interface.
• 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, the methods a class provides, should not depend on how the attributes are
represented.
• Ex: we developed earlier a class that represents a time of day. Methods provided by this class
include time_to_int, is_after, and add_time.
• These methods can be implemented in several ways and depend on how time is represented.
• The attributes defined for a Time object are hour, minute, and second.
• As an alternative, these attributes can be replaced 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.
• If the interface is designed carefully, the implementation can be changed without changing
the interface, which means that other parts of the program don’t have to change.
Glossary for quick glance:
class A programmer-defined type. A class definition creates a new class object
class object An object that contains information about a programmer-defined type. The class
object can be used to create instances of the type.
instance An object that belongs to a class
instantiate To create a new object
attribute One of the named values associated with an object
embedded An object that is stored as an attribute of another object
object
shallow copy To copy the contents of an object, including any references to embedded objects
implemented by the copy function in the copy module
5-12
Module-5: Classes, objects and methods
deep copy To copy the contents of an object as well as any embedded objects, and any objects
embedded in them, and so on; implemented by the deepcopy function in the copy
module
object A diagram that shows objects, their attributes, and the values of the attributes
diagram
prototype A development plan that involves writing a rough draft of a program, testing, and
and patch correcting errors as they are found
designed A development plan that involves high-level insight into the problem and more
development planning than incremental development or prototype development.
pure function A function that does not modify any of the objects it receives as arguments.
modifier A function that changes one or more of the objects it receives as arguments
functional A style of program design in which the majority of functions are pure
programming
style
invariant A condition that should always be true during the execution of a program
assert A statement that check a condition and raises an exception if it fails
statement
5-13