0% found this document useful (0 votes)
59 views48 pages

ENGR 101 Introduction To Programming: Week 14

The document discusses object-oriented programming concepts like classes, inheritance, and polymorphism. It provides examples of modeling vehicles like cars, trucks, and motorcycles in a car dealership using classes, with the base Vehicle class facilitating code reuse through inheritance. The document also demonstrates how polymorphism allows functions to work with different types of objects like modeling different kinds of talking animals.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
59 views48 pages

ENGR 101 Introduction To Programming: Week 14

The document discusses object-oriented programming concepts like classes, inheritance, and polymorphism. It provides examples of modeling vehicles like cars, trucks, and motorcycles in a car dealership using classes, with the base Vehicle class facilitating code reuse through inheritance. The document also demonstrates how polymorphism allows functions to work with different types of objects like modeling different kinds of talking animals.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PPTX, PDF, TXT or read online on Scribd
You are on page 1/ 48

ENGR 101

Introduction to
Programming
Week 14
Reminder
Last Week (Week 13)
Classes and Methods
class Time:
def __init__(self, hour=0, minute=0, second=0):
self.hour = hour
self.minute = minute
self.second = second

def __str__(self):
return str(self.hour) + ':' + str(self.minute) + ':' + str(self.second)

def print_time(self):
print str(self.hour) + ':' + str(self.minute) + ':'\
+ str(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):
hour = (seconds / 3600) % 24
minute = (seconds /60) % 60
second = seconds % 60
return Time(hour, minute, second)

def increment(self, seconds):


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

def is_after(self, other):


return self.time_to_int() >other.time_to_int()
#Operator overloading
def __add__(self, other):
seconds = self.time_to_int() + other.time_to_int()
return self.int_to_time(seconds)

# add time to add time object


def add_time(self, other):
seconds = self.time_to_int() + other.time_to_int()
return self.int_to_time(seconds)

# Type-based dispatch
def __add__(self, other):
if isinstance(other, Time):
return self.add_time(other)
else:
return self.increment(other)

# Right side addition


def __radd__(self, other):
return self.__add__(other)
Week 14
Inheritance
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)
Dealership - Motorcycle Class
class Motorcycle:
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 4000- (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, Truck and Motorcycle classes from Vehicle
Dealership – Vehicle Class
class Vehicle:
flat_rate = 0
wheels = 0
def __init__(self, kilometers, make, model, year):
"""Return a new Vehicle object."""
self.kilometers = kilometers
self.make = make
Our “base” class
self.model = model that
self.year = year
accommodates
def sale_price(self): the shared parts
return 5000 * self.wheels

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

def vehicle_advertisement_text(self):
pass
Inheritance: Car and Truck
class Car(Vehicle):
flat_rate = 8000
wheels = 4
Syntax for
inheritance
def vehicle_advertisement_text (self):
return 'Car - ' + str(self.year) + ' ' + self.make + ' ' + \
self.model + ' - ' + str(self.kilometers) + ' km'

class Truck(Vehicle):
flat_rate = 10000 Method
wheels = 10
overriding
def vehicle_advertisement_text (self):
return ‘Truck - ' + str(self.year) + ' ' + self.make + ' ' + \
self.model + ' - ' + str(self.kilometers) + ' km'
Adding a new class: Motorcycle

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

def vehicle_advertisement_text (self):


return Motorcycle - ' + str(self.year) + ' ' + self.make + ' ' + \
self.model + ' - ' + str(self.kilometers) + ' km'
Problems?

• Did we solve all problems?


• Not quite! The following method still looks very similar
and being repeated in different child classes.
 vehicle_advertisement_text

• Solution:
 Move the common/repeated part to the base class under
vehicle_advertisement_text method.
 Call base class’es vehicle_advertisement_text
method in child classes’
vehicle_advertisement_text method.
class Vehicle(object):
... Move the common parts to
... Inherit the built-in
... the base class method
object class
def vehicle_advertisement_text(self):
return str(self.year) + ' ' + self.make + ' ' + \
self.model + ' - ' + str(self.kilometers) + ' km'

class Car(Vehicle): Call base class method in


flat_rate = 8000 child classes
wheels = 4
def vehicle_advertisement_text (self):
return 'Car - ' + super(Car, self).vehicle_advertisement_text()

class Truck(Vehicle):
flat_rate = 10000
wheels = 10
def vehicle_advertisement_text (self):
return 'Truck - ' + super(Truck, self).vehicle_advertisement_text()

class Motorcycle(Vehicle):
flat_rate = 4000
wheels = 2
def vehicle_advertisement_text(self):
return 'Motocycle-' + super(Motorcycle, self).vehicle_advertisement_text()

car = Car(70000, 'Toyota', 'Corolla', 2008)


print car.vehicle_advertisement_text()
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.
Animal World

class Cat:
def __init__(self, name):
self.name = name

class Dog:
def __init__(self, name):
self.name = name
Talking Animals

animals = [Cat('Missy'),
Cat('Mistof'),
Dog('Lassie')]

for animal in animals:


if isinstance(animal, Cat):

print animal.name + ': ’ + ‘Meow’


elif isinstance(animal, Dog):
print animal.name + ': ’ + ‘Woof’

# output:

# Missy: Meow!
# Mistof: Meow!
# Lassie: Woof!
Problem?

• What if we have 1000 different kinds of animals rather


than just Cat and Dog?

 1000 elif statements?


Solution: Polymorphism
class Animal(object):
def __init__(self, name):
self.name = name

def talk(self):
return ‘what?’

class Cat(Animal):
def talk(self):
return 'Meow!'

class Dog(Animal):
def talk(self):
return 'Woof!'
Solution: Polymorphism

animals = [Cat('Missy'),
Cat('Mistof'),
Dog('Lassie')]

for animal in animals:


print animal.name + ': ' + animal.talk()

# output:
#
# Missy: Meow!
# Mistof: Meow!
# Lassie: Woof!
Polymorphism

• Functions that can work with several types are called


polymorphic.
• Polymorphism can facilitate code reuse.
• For example, the built-in function sum, which adds the
elements of a sequence, works as long as the elements of
the sequence support addition.
Polymorphism

• 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:1:0

print sum([1, 2, 3])


6
Polymorphism
• vehicle_advertisement_text() method in Vehicle,
Car, Truck, Motorcycle classes is another example
of polymorphism that we worked with as it worked
for different objects.
• Another example:
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
my_string="banana"
my_list=["spam","egg","spam","spam","bread"]
my_tuple=("a","b","c","b","a","a","a","d")
#Histogram is a polymorphic function because it works for different types:
# strings, lists, tuples and even dictionaries.
print histogram(my_string)
print histogram(my_list)
print histogram(my_tuple)
Modeling Card Objects
Hearts Diamonds

Clubs
Spades

A deck of cards
Card objects

• There are fifty-two cards in a deck, each of which


belongs to one of four suits and one of thirteen ranks.
• The suits are Spades, Hearts, Diamonds, and Clubs (in
descending order).
• The ranks are Ace, 2, 3, 4, 5, 6, 7, 8, 9, 10, Jack, Queen,
and King.
• Depending on the game that you are playing, an Ace
may be higher than King or lower than 2.
Class attributes

class Card(object):
"""represents a standard playing card."""

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


rank_names = [None, 'Ace', '2', '3', '4', '5', '6', '7',

'8', '9', '10', 'Jack', 'Queen', 'King']

def __init__(self, suit=0, rank=2):


self.suit = suit
self.rank = rank

def __str__(self):
return Card.rank_names[self.rank] + ‘ of ’ + \
Card.suit_names[self.suit]
Comparing cards –
Operator Overloading

• 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 user-defined types, we can override the behavior of the built-
in operators by providing a method named __cmp__.
• __cmp__ takes two parameters, self and other, and returns a
positive number if the first object is greater, a negative number if
the second object is greater, and 0 if they are equal to each other.
Comparing cards – Operator
Overloading

# inside class Card:


def __cmp__(self, other):
# check the suits
if self.suit > other.suit: return 1
if self.suit < other.suit: return -1

# suits are the same... check ranks


if self.rank > other.rank: return 1
if self.rank < other.rank: return -1

# ranks are the same... it's a tie


return 0
Comparing cards

• You can write this more concisely using tuple


comparison:

# inside class Card:


def __cmp__(self, other):
t1 = self.suit, self.rank
t2 = other.suit, other.rank
return cmp(t1, t2)
Decks

• We have Cards, lets define a Deck of cards.

class Deck(object):

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

deck = Deck()
print deck
Ace of Clubs
#inside class Deck:
def __str__(self): 2 of Clubs
res = [] 3 of Clubs
for card in self.cards: ...
res.append(str(card))
return '\n'.join(res) 10 of Spades
Jack of Spades
Queen of Spades
King of Spades
Add, remove, shuffle,
and sort

• To distribute cards, we need a method that removes a


card from the one end of the deck and returns it.
• The list method, “pop”, provides a convenient way to do
that:

#inside class Deck:


def pop_card(self):
return self.cards.pop()
Add, remove, shuffle
and sort

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


#inside class Deck:
def add_card(self, card):
self.cards.append(card)
Add, remove, shuffle
and sort

• Lets define method to shuffle cards: using the function


shuffle from the random module:

# inside class Deck:


def shuffle(self):
random.shuffle(self.cards)
Inheritance - Parent/Child

• Inheritance is the ability to define a new class that is a


modified version of an existing class.
• It is called “inheritance” because the new class inherits
the methods and attributes of the existing class.
Extending this metaphor, the existing class is called the
parent and the new class is called the child.
• As an example, let’s say we want a class to represent a
“hand,” that is, the set of cards held by one player.
Inheritance –
Similar/Different

• A hand is similar to a deck:


 both are made up of a set 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, e.g., comparison.
• This relationship between classes —similar, but different
— lends itself well to inheritance.
Inheritance - Declaration

• The definition of a child class is like other class


definitions, but the name of the parent class appears 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 Hand objects as well as Deck objects.
Inheritance - Overriding

• Hand also inherits __init__ from Deck, but it doesn’t 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.
• 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
Inheritance - Overriding

• So when you create a Hand, Python invokes this init


method:
hand = Hand('new hand')
print hand.cards
[]
print hand.label
new hand
Inheritance

• But 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
Deal a hand

#inside class Deck:


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

• Write a Deck method called deal_hands that takes two


parameters, the number of hands and the number of
cards per hand, and that creates new Hand objects, deals
the appropriate number of cards per hand, and returns a
list of Hand objects.
Deal a hand

#inside class Deck:


def deal_hands(self, hand_num, card_num):
hands = []
for i in range(hand_num):
hand = Hand(‘hand ’ + str(i))
self.move_cards(hand, card_num)
hands.append(hand)
return hands
Exercises
Exercise 1:
Write a class Game with attributes:
• energy,
• money and
• no_of_castles
and a show_info (which will print the attributes’ values)
method.

Write a class named Player that will inherit from Game class
and implement a create_new_castle method
which will allow the player to create a new castle if his
energy is >5 and his money is > 10,
it should update the attributes accordingly.
Exercise 2:
• Create another class OponentPlayer that will inherit from
class Game written before and
it will have an additional attribute named
no_of_destructions.

Implement a method that will destroyCastle() which will allow
the player to destroy a castle, thereby increasing his
no_of_destructions attribute.

If the player’s energy is lower than 10, he won’t be able to
destroy the castle.

• Attributes should be updated properly following destruction.

You might also like