Classes and
Functions
Classes Inheritance
and
objects Module 5
Classes and
Methods
Classes
and
objects
Programmer-defined types
• We have used many of Python’s built-in types; now we are going to define a new type.
• As an example, we will create a type called Point that represents a point in two
dimensional space.
• There are several ways we might represent points in Python:
• We could store the coordinates separately in two variables, x and y.
• We could store the coordinates as elements in a list or tuple.
• We could create a new type to represent points as objects.
class Point:
"""Represents a point in 2-D space."""
>>> blank = Point()
Creating a new object is called instantiation, and the object is an instance of the class.
Attributes
• You can assign values to an instance using dot notation:
>>> blank.x = 3.0
>>> blank.y = 4.0
You can read the value of an attribute using the same syntax:
>>> blank.y You can pass an instance as an
4.0 argument in the usual way. For
>>> x = blank.x example:
>>> x def print_point(p):
3.0 print('(%g, %g)' % (p.x, p.y))
You can use dot notation as part of any expression. For example:
>>> distance = math.sqrt(blank.x**2 + blank.y**2)
>>> distance
5.0
Rectangles
• Sometimes it is obvious what the attributes of an object should be, but other times you
have to make decisions.
• For example, imagine you are designing a class to represent rectangles. What attributes
would you use to specify the location and size of a rectangle
• 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.
box = Rectangle()
box.width = 100.0
box.height = 200.0
box.corner = Point()
box.corner.x = 0.0
box.corner.y = 0.0
Instances as return values
• Functions can return instances.
>>> center = find_center(box)
def find_center(rect): >>> print_point(center)
p = Point() (50, 100)
p.x = rect.corner.x + rect.width/2
p.y = rect.corner.y + rect.height/2
return p
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
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)
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:
>>> p1 = Point()
>>> p1.x = 3.0 >>> print_point(p1)
(3, 4)
>>> p1.y = 4.0
>>> print_point(p2)
>>> import copy (3, 4)
>>> p2 = copy.copy(p1) >>> p1 is p2
False
Copying cont….
• 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
• 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.
>>> box3 = copy.deepcopy(box)
>>> box3 is box
False
>>> box3.corner is box.corner
False
Exercise
• Exercise 1
• Write a definition for a class named Circle with attributes center and radius, where center
is a Point object and radius is a number. Instantiate a Circle object that represents a circle
with its center at (150, 100) and radius 75.
• Write a function named point_in_circle that takes a Circle and a Point and returns True if
the Point lies in or on the boundary of the circle.
• Write a function named rect_in_circle that takes a Circle and a Rectangle and returns True
if the Rectangle lies entirely in or on the boundary of the circle.
• Write a function named rect_circle_overlap that takes a Circle and a Rectangle and returns
True if any of the corners of the Rectangle fall inside the circle.
Classes
and
functions
Time
• we’ll define a class called Time that records the time of day.
class Time:
"""Represents the time of day.
attributes: hour, minute, second
""“
time = Time()
time.hour = 11
time.minute = 59
time.second = 30
Pure functions
• two kinds of functions: pure functions and modifiers
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
• The function creates a new Time object, initializes its attributes, and returns a reference to
the new object. This is called a pure function.
• Code on 156 & 157
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.
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
Prototyping versus planning
• The development plan used here is called “prototype and patch”.
• For each function, we 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.
• An alternative is designed development, in which high-level insight into the problem can
make the programming much easier. In this case, the insight is that a Time object is really
a three-digit number in base 60
Classes and methods
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 often correspond to
the ways things in the real world interact.
• 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
class Time:
"""Represents the time of day."""
def print_time(time):
print('%.2d:%.2d:%.2d' % (time.hour, time.minute, time.second))
• To make print_time a method, all we have to do is move the function definition inside the class
definition.
class Time:
def print_time(time):
print('%.2d:%.2d:%.2d' % (time.hour, time.minute, time.second))
• Now there are two ways to call print_time. The first (and less common) way is to use function
syntax:
>>> Time.print_time(start)
• The second (and more concise) way is to use method syntax:
>>> start.print_time()
Printing objects cont….
• 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))
• The reason for this convention is an implicit metaphor:
• The syntax for a function call, print_time(start), suggests that the function is the
active agent. It says something like, “Hey print_time! Here’s an object for you to
print.”
• In object-oriented programming, the objects are the active agents. A method
invocation like start.print_time() says “Hey start! Please print yourself.”
As an exercise, rewrite time_to_int as a method.
Another example
• Here’s a version of increment rewritten as a method:
# inside class Time:
def increment(self, seconds):
seconds += self.time_to_int()
return int_to_time(seconds)
• Here’s how you would invoke increment:
>>> start.print_time()
09:45:00
>>> end = start.increment(1337)
>>> end.print_time()
10:07:17
A more complicated example
• Rewriting is_after is slightly more complicated because it takes two Time objects as
parameters.
# inside class Time:
def is_after(self, other):
return self.time_to_int() > other.time_to_int()
• To use this method, you have to invoke it on one object and pass the other as an
argument:
>>> end.is_after(start)
True
The init method
• The init method (short for “initialization”) is a special method that gets invoked when an
object is instantiated.
# inside class Time:
def __init__(self, hour=0, minute=0, second=0):
self.hour = hour
self.minute = minute
self.second = second
• 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
The __str__ method
• __str__ is a special method, like __init__, that is supposed to return a string
representation of an object.
• For example, 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
Operator overloading
• By defining other special methods, you can specify the behavior of operators on
programmer-defined types.
• For example, 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)
• And here is how you could use it:
>>> start = Time(9, 45)
>>> duration = Time(1, 35) Changing the behavior of an operator so
that it works with programmer-defined
>>> print(start + duration)
types is called operator overloading.
11:20:00
Type-based dispatch
• 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:
# 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()
return int_to_time(seconds)
Type-based dispatch cont….
• This operation is called a type-based dispatch because it dispatches the computation to
different methods based on the type of the arguments.
• Unfortunately, this implementation of addition is not commutative. If the integer is the
first operand, you get
>>> print(1337 + start)
TypeError: unsupported operand type(s) for +: 'int' and 'instance‘
• 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.
# inside class Time:
def __radd__(self, other):
return self.__add__(other)
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
• that work with several types are called polymorphic.
• Polymorphism can facilitate code reuse.