ENGR 101
Introduction to
Programming
Week 12
Reminder
Last Week (Week 11)
Dictionaries
Dictionary – What is it?
• A dictionary is like a list, but more general.
In a list, the indices have to be integers; in a dictionary
they can be (almost) any type.
• You can think of a dictionary as a mapping between a set
of indices (which are called keys) and a set of values.
• Each key maps to a value. The association of a key and a
value is called a key-value pair or sometimes an item.
Getting all keys or values
hist = {'a': 1, 'p': 1, 'r': 2, 't': 1}
print hist.keys()
[‘a’, ‘p’, ‘r’, ‘t’]
print hist.values()
[1, 1, 2, 1]
• Note: Keys have to be immutable!
Dictionary as a set of counters
# counters implementation
def histogram(s):
d = dict()
for c in s:
if c not in d:
d[c] = 1
else:
d[c] += 1
return d
The name of the function is histogram, which is a
statistical term for a set of counters (or frequencies).
Looping and dictionaries
#implementation
def print_hist(h):
for c in h:
print c, h[c]
h = histogram('parrot')
print_hist(h)
a 1
p 1
...
Reverse lookup
• Given a dictionary d and a key k, it is easy to find the
corresponding value v which is d[k]. This operation is
called a lookup.
• But, what if you have v and you want to find k?
# implementation
def reverse_lookup(d, v):
for k in d:
if d[k] == v:
return k
return -1
Dictionaries and lists
# inversion implementation
def invert_dict(d):
inv = dict()
for key in d:
val = d[key]
if val not in inv:
inv[val] = [key]
else:#keys appended as a list for same value
inv[val].append(key)
return inv
Dictionaries and lists
hist = histogram('parrot')
print hist
{'a': 1, 'p': 1, 'r': 2, 't': 1,
'o': 1}
inv = invert_dict(hist)
print inv
{1: ['a', 'p', 't', 'o'], 2: ['r']}
Week 12
Classes and Objects
A new programming paradigm
• So far, we have designed our programs around functions, i.e.
blocks of statements, which manipulate data.
procedural programming
• An alternative way: combine data and functionality and wrap it
inside something called an object.
object-oriented programming
• Why do we need this new way of programming?
When writing large programs, it is a lot easier to develop
and maintain programs in object-oriented manner.
Object oriented programming
• Two main aspects of OOP:
Classes and objects
• Class: creates a new type (blueprint for objects)
• Objects are instances of the class.
• All family cars [a class] have 4 wheels and an engine.
My Corolla [an instance/object] is broken yesterday.
There is a taxi [an instance/object] parked outside.
Object-oriented features
• Programs are made up of
object definitions,
function definitions
• Most of the computation is expressed in terms of
operations on objects.
• Object definition an object/concept in real world
• Functions that operate on that object the ways real-
world objects interact.
Classes
Contains two essential pieces:
• Attributes: properties of objects
color of a car, engine type, wheel size, etc.
• Methods (i.e., functions on objects)
start_engine(…)
run_wipers(…)
turn_on_headlights(…)
Classes: Syntax
class ClassName:
def __init__(self, …):
Attribute initializations
def method1_name(self, …):
…
def method2_name(self, …):
…
Classes: User-defined types
• Create a class called Point that represents a point in two-
dimensional space.
• Points are often written in parentheses with a comma
separating the coordinates.
(0, 0) represents the origin
(x, y) represents the point x units to the right and y
units up from the origin.
Classes: User-defined types
• Several ways to represent points in Python:
Store the coordinates separately in two variables, x
and y.
Store the coordinates as elements in a list or tuple.
Create a new type, i.e., a class, to represent points as
objects.
Classes: A first example
class Point:
"""represents a point in 2-D space"""
def __init__(self, x, y):
self.x = x
self.y = y
• A class is like a factory for creating objects. To create a
Point object, you call Point as if it were a function.
blank = Point(0, 0)
Instances
• Creating a new object is called instantiation, and the
object is an instance of the class.
• 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).
blank = Point(0, 0)
print blank
<__main__.Point instance at 0xb79d3ac>
Attributes
• Access attributes of an instance using dot notation:
print blank.x, blank.y
0 0
blank.x = 3.0
blank.y = 4.0
Instances & Attributes
print blank.x, blank.y
3.0 4.0
distance = math.sqrt(blank.x**2 + blank.y**2)
print distance
5.0
• You can also pass an instance as an argument:
def print_point(p):
print ‘(’ + str(p.x) + ‘,’ + str(p.y) + ‘)’
The init method
• The init method (short for “initialization”) is a special
method that gets invoked when an object is created.
• Its full name is __init__ (two underscore characters,
followed by init, and then two more underscores).
class Time:
def __init__(self, hour=0, minute=0, second=0):
self.hour = hour
self.minute = minute
self.second = second
Printing Time objects
def print_time(t):
print str(t.hour) + ‘:’ + \
str(t.minute) + ‘:’ + \
str(t.second)
The init method
• The parameters are optional, so if you call Time with no
arguments, you get the default values.
time = Time()
print_time(time)
0:0:0
• If you provide one argument, it overrides hour:
time = Time(9)
print_time(time)
9:0:0
The init method
• If you provide two arguments, they override hour and
minute.
time = Time(9, 45)
print_time(time)
9:45:0
• And if you provide three arguments, they override all
three default values.
Methods
Methods
• Methods are semantically the same as functions, but
there are three syntactic differences:
Defined inside a class definition
The syntax for invoking a method is different from the
syntax for calling a function.
The first parameter of a method is always ‘self’ (self
is not a reserved word, but commonly used as a
convention).
Methods – Printing time
• Define a method called print_time:
# implementation
class Time:
def print_time(self):
print str(self.hour) + ‘:’ + \
str(self.minute) + ‘:’ + \
str(self.second)
• Self refers to the current object that the method is called
on, or being created.
Methods – Printing time
class Time:
def print_time(self):
print str(self.hour) + ‘:’ + \
str(self.minute) + ‘:’ + \
str(self.second)
start = Time(9, 45)
Time.print_time(start) # method call via class name
09:45:0
start.print_time() # method call via a class instance
09:45:0
In-Class Exercise
• Implement time_to_int() method.
# inside class Time
def time_to_int(self):
minutes = self.hour * 60 + self.minute
seconds = minutes * 60 + self.second
return seconds
In-Class Exercise
• Implement int_to_time() method.
# inside class Time
def int_to_time(self, seconds):
hour = (seconds/3600) % 24
minute = (seconds / 60)%60
second = seconds % 60
return Time(hour, minute, second)
Methods – Another exercise
# inside class Time
def increment(self, seconds):
tot_secs = seconds + self.time_to_int()
return self.int_to_time(tot_secs)
Methods
Start = Time(9, 45)
start.print_time()
09:45:0
end = start.increment(1337)
end.print_time()
10:7:17
• Sometimes it may be confusing due to self param.
end = start.increment(1337, 460)
TypeError: increment() takes exactly 2
arguments (3 given)
Methods
• is_after 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:
class Time:
def __init__(self, hour=0, min=0, sec=0):
self.hour = hour
self.minute = min
self.second = sec
def print_time(self):
print str(self.hour) + ':' + str(self.minute) + \
':' + str(self.second)
def time_to_int(self):
total_seconds = self.hour * 3600
total_seconds += self.minute * 60
total_seconds += self.second
return total_seconds
def int_to_time(self, tot_secs):
hour = (tot_secs / 3600) % 24
minute = (tot_secs % 3600) / 60
second = tot_secs % 60
t = Time(hour, minute, second)
return t
def increment(self, x):
total_secs = self.time_to_int() + x
return self.int_to_time(total_secs)
time1 = Time(10, 30)
time1.print_time()
print time1.time_to_int()
time2 = time1.int_to_time(3700)
time1.print_time()
time2.print_time()
time3 = time2.increment(20)
time3.print_time()
time2.print_time()
Output:
10:30:0
37800
10:30:0
1:1:40
1:2:0
1:1:40
import math
class Point:
def __init__(self, x ,y):
self.x = x
self.y = y
def get_dist_from_origin(self):
return math.sqrt(self.x**2 + self.y**2)
def get_dist_from_another_point(self, p):
return math.sqrt((self.x - p.x)**2 + (self.y - p.y)**2)
point1 = Point(3, 5)
point2 = Point(0, 0)
print point1.get_dist_from_origin()
print point1.get_dist_from_another_point(point2)
Output:
5.83095189485
5.83095189485
Rectangles
• 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?
Rectangles
• There are at least two possibilities:
You could specify one corner of the rectangle, the
width, and the height.
You could specify two opposing corners.
Rectangles
class Rectangle:
"""represent a rectangle.
attributes: width, height, corner.
"""
def __init__(self, width, height, x, y):
self.width = width
self.height = height
self.corner = Point(x, y)
Rectangles
• Instantiate a Rectangle object and assign values to the
attributes:
box = Rectangle(100, 200, 0, 0)
Rectangles
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
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:
# implementation
def find_center(box):
p = Point()
p.x = box.corner.x + box.width/2.0
p.y = box.corner.y + box.height/2.0
return p
Copying
• Aliasing can make a program difficult to read
changes in one place might have unexpected effects
in another place.
hard to keep track of all the variables that might
refer to a given object.
• Copying an object is often an alternative to aliasing.
“copy” module
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.
Copying
• If you use copy.copy to duplicate a Rectangle, note that it copies
the Rectangle object but not the embedded Point.
box = Rectangle(100, 200, 0, 0)
box2 = copy.copy(box)
print box2 is box
False
print box2.corner is box.corner
True
Copying
• This operation (copy.copy) is called a shallow copy because it
copies the object, but not the embedded objects.
• The copy module contains 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. This operation is called a deep copy.
box3 = copy.deepcopy(box)
print box3 is box
False
print box3.corner is box.corner
False
Debugging
• If you try to access an attribute that doesn’t exist:
p = Point(0, 0)
print p.z
AttributeError: Point instance has no
attribute 'z'
• To check whether an object has a particular attribute:
print hasattr(p, ‘z')
False
• If you are not sure what type an object is, you can ask:
print type(p)
<type '__main__.Point’>
Exercises
Exercise 1
• Define a class Employee that will have as
attributes name and salary.
• Whenever you create
a new employee instance it should print
out “You added a new employee, their name
is: <given name>”.
• Add a method to change to be able to
update the salary.
Exercise 2
• Define a class Student that has two attributes: name
and a dictionary of courses by semester
(e.g. {‘Semester4’:{‘PHYS101’:3, ‘CS 100’:1}}
Note: the values for course codes are credits.
• Add a method that takes an argument like ‘SemesterX’ as
input (X can be 1-8) and
prints all the courses student took in that semester
along with total number of credits that semester.
>> student1 = Student("Alper", {"Semester1":{"PHYS
101":3, "MATH 101":3}})
>> student1.semester_history("Semester1")
PHYS 101
MATH101
Total credits in Semester1: 6