You are on page 1of 28

ENGR 101

Introduction to
Programming
Week 13
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
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)
The __str__ method
• __str__ is a special method, like __init__, that is
supposed to return a string representation of an
object.
# inside class Time:
def __str__(self):
return str(self.hour) + ‘:’ + \
str(self.minute) + ‘:’ + \
str(self.second)
time = Time(9, 45)
print time
9:45:0
Exercise

• Write a str method for the Point class. Create a


Point object and print it.
Operator overloading
• By defining other special methods, you can specify
the behavior of operators on user-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)
Operator overloading
start = Time(9, 45)
duration = Time(1, 35)
print start + duration
11:20:0
• When you apply the + operator to Time objects,
Python invokes __add__.
• When you print the result, Python invokes __str__.
• So there is quite a lot happening behind the
scenes!
Exercise

• Write an add method for the Point class.


Operator overloading

• Changing the behavior of an operator so that it


works with user-defined types is called operator
overloading.
• For every operator in Python there is a
corresponding special method, like __add__.
• For more details, see
https://docs.python.org/2/reference/datamodel.html
Type-based dispatch
• Here are examples that use the + operator with
different types:
start = Time(9, 45)
duration = Time(1, 35)
print start + duration
11:20:0
print start + 5700
????
Type-based dispatch
# 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):


...

def increment(self, seconds):


...
Type-based dispatch
• Here are examples that use the + operator with
different types:
start = Time(9, 45)
duration = Time(1, 35)
print start + duration
11:20:0
print start + 5700
11:20:0
Type-based dispatch
• This implementation of addition is not commutative.
print start + 1337
10:7:17
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 do that.
Type-based dispatch
• There is a clever solution for this problem:
• Overload a special method __radd__, which
stands for “right-side add.”
# inside class Time:
def __radd__(self, other):
return self.__add__(other)
And here’s how it’s used:
print 1337 + start
10:7:17
Take-home Assignment
• Write an add method for Point that works with either
a Point object or a tuple:
• If the second operand is a Point, the method
should return a new Point whose x coordinate is
the sum of the x coordinates of the operands, and
likewise for the y coordinates.
• If the second operand is a tuple, the method
should add the first element of the tuple to the x
coordinate and the second element to the y
coordinate, and return a new Point with the result.
Debugging
• good idea to initialize all attributes in the init
method.
• not sure whether an object has a particular
attribute?
• hasattr(obj, attr)
• getattr(obj, attr)
• __dict__
Debugging
p = Point(3, 4)
print p.__dict__
{'y': 4, 'x': 3}

def print_attributes(obj):
for attr in obj.__dict__:
print attr, getattr(obj, attr)
Car Dealership
• Imagine we run a car dealership!
• Selling all types of vehicles from trucks to motorcycles.
• Vehicle price: $5000 x num_of_wheels
• Buying back a car: flat_rate – 10% of kilometers
• flat_rate = 10,000 for trucks
• flat_rate = 8,000 for cars
• flat_rate = 4,000 for motorcycles
Car Dealership

• Design an object oriented sales system for the


dealership!
• What would be the objects?
• Car, Truck, Motorcycle
• Sale, Customer, Inventory, …
Dealership - Car Class
class Car:
wheels = 4
def __init__(self, kilometers, make, model, year):
"""Return a new Car object."""
self.kilometers = kilometers
self.make = make
self.model = model
self.year = year

def sale_price(self):
"""Return the sale price for this car."""
return 5000 * self.wheels

def purchase_price(self):
"""Return the price for which we would pay to purchase the car."""
return 8000 - (0.10 * self.kilometers)
Dealership - Truck Class
class Truck:
wheels = 10
def __init__(self, kilometers, make, model, year):
"""Return a new Truck object."""
self.kilometers = kilometers
self.make = make
self.model = model
self.year = year

def sale_price(self):
"""Return the sale price for this truck"""
return 5000 * self.wheels

def purchase_price(self):
"""Return the price for which we would pay to purchase the truck."""
return 10000 - (0.10 * self.kilometers)
Problems?
• Any problems so far?
• The two classes are almost identical!
• DRY (Don’t Repeat Yourself!)
• Solution:
• Inheritance!: Introduce base class that will capture
the shared parts of Car and Truck classes
• Add Vehicle class as our base class
• Derive Car and Truck classes from Vehicle
Inheritance
Dealership – Vehicle Class
class Vehicle:
base_sale_price = 0
wheels = 0
def __init__(self, kilometers, make, model, year):
"""Return a new Vehicle object."""
self.kilometers = kilometers
self.make = make
self.model = model
Our Base
self.year = year Class for
Inheritance
def sale_price(self):
return 5000 * self.wheels

def purchase_price(self):
return self.base_sale_price - (0.10 * self.kilometers)

def vehicle_type(self):
pass
Inheritance: Car and Truck
class Car(Vehicle):
base_sale_price = 8000
wheels = 4
Syntax for
inheritance
def vehicle_type(self):
return ‘Car’

class Truck(Vehicle):
base_sale_price = 10000
wheels = 10
Method
def vehicle_type(self): overriding
return ‘Truck’
Adding a new class: Motorcycle

class Motorcycle(Vehicle):
base_sale_price = 4000
wheels = 2

def vehicle_type(self):
return ‘Motorcycle’
Problems?
• Did we solve all problems?
• Not quite!
v = Vehicle(0, 'Honda', 'Civic', 2018)

• The above should be disallowed as Vehicle class is an


“abstract” class only created for inheritance.
• It is not meant for real object creation!
Solution: Abstract Base Class
from abc import ABCMeta, abstractmethod
class Vehicle:
base_sale_price = 0
wheels = 0
Turning a class into ABC
(Abstract Base Class):
__metaclass__ = ABCMeta
def __init__(self, kilometers, make, model, year): 1. Set __metaclass__
"""Return a new Vehicle object.""" attribute
self.miles = kilometers
2. Label at least one
self.make = make method as abstract
self.model = model
The above changes will
self.year = year
make sure that Vehicle
cannot be instantiated.
def sale_price(self):
return 5000 * self.wheels

def purchase_price(self):
return self.base_sale_price - (0.10 * self.kilometers)

@abstractmethod
def vehicle_type(self):
pass
Inheritance - Thoughts

• Inheritance is a useful feature.


• Inheritance can facilitate code reuse, since you can
customize the behavior of parent classes without
having to modify them.
• 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 scattered among several modules.

You might also like