Let's start with a very simple class called Person
which holds some state (instance variables) and behaviors (methods).
All people have names and ages, so those are going to be our instance variables. It's also nice to have a method that will increment the age of a person on their birthday.
class Person:
def __init__(self, name, age):
"""Constructor to initialize instance variables for name and age.
Name is represented as 'First Last'"""
self.age = age
self.name = name
def getFirstName(self):
"""Returns the person's first name"""
return self.name.split()[0]
def printGreeting(self):
"""Greets this person and tells them their last name"""
print "Hello {name}. You are {age} years old.".format(name=self.name, age=self.age)
def todayBirthday(self):
"""Increment a person's age and wish them happy birhtday"""
self.age = self.age + 1
print "Happy birthday", self.getFirstName() + "!"
Now let's create some Person
objects by invoking the constructor.
taylor = Person("Taylor Swift", 26)
bruno = Person("Bruno Mars", 30)
serena = Person("Serena Williams", 34)
rafa = Person("Rafael Nadal", 29)
Here's how to invoke an object's behaviors.
rafa.printGreeting()
serena.todayBirthday()
serena.printGreeting()
We sometimes want to print out the current state of an object. Can I do the following?
print serena
Not very informative, is it?
There's a special method called __repr__
that lets you specify a string representation of an object. We're going to add it to the Person
class.
class Person:
def __init__(self, name, age):
"""Constructor to initialize instance variables for name and age.
Name is represented as 'First Last', and we'll store them separately"""
self.age = age
self.name = name
def getFirstName(self):
"""Returns the person's first name"""
return self.name.split()[0]
def printGreeting(self):
"""Greets this person and tells them their last name"""
print "Hello {name}. You are {age} years old.".format(name=self.name, age=self.age)
def todayBirthday(self):
"""Says that today is this person's birthday, so increment their age and wish them"""
self.age = self.age + 1
print "Happy birthday", self.getFirstName() + "!"
def __repr__(self):
"""String representation of object's state"""
return "<Person - name: {n}; age: {a}>".format(n=self.name, a=self.age)
# Alternative solution with string concatenation, not formatting
#return '<Person - name: '+ self.name + '; age: '+ str(self.age)+'>'
NOTE: The reason for showing an example of the format
method is that differently from string concatenation,
it doesn't require the user to convert values to string, it takes care of it as part of the string formatting.
It is also similar to the Jinja2 examples we saw in Lecture 20.
Now let's try defining the objects and printing them again.
taylor = Person("Taylor Swift", 26)
bruno = Person("Bruno Mars", 30)
serena = Person("Serena Williams", 34)
rafa = Person("Rafael Nadal", 29)
print taylor
str(taylor)
Now it's Taylor's birthday!
taylor.todayBirthday()
print taylor
taylor
MutableString
¶Let's define a class named MutableString
that behaves like Python's str
class, except that it is mutable.
It should support the following behaviors that str
has:
In addition, it should support the following mutable behaviors not in str
:
Consider two different implementations:
(use the variable chars that is a list of characters)
class MutableString:
def __init__(self, data):
self.chars = list(data)
def length(self):
"return the length of the MutableString object"
return len(self.chars)
def getval(self, index):
"return the character at index"
return self.chars[index]
def setval(self, index, value):
"change the value of the MutableString object at index to c"
self.chars[index] = value
def reverse(self):
"change the value of the MutableString object to its reverse"
self.chars.reverse()
def __repr__(self):
"specify how the MutableString object is displayed"
return ''.join(self.chars)
Test this out below.
s = MutableString('deer')
print 's is:', s
print 's.length() is:', s.length()
print 's.getval(3) is:', s.getval(3)
s.reverse()
print 'after s.reverse(), s is:', s
s.setval(0, 's')
print "after s.setval(0, 's'), s is:", s
s
(use the variable string that hold the string)
class MutableString:
def __init__(self, data):
self.string = data
def length(self):
"return the length of the MutableString object"
return len(self.string)
def getval(self, index):
"return the character at index"
return self.string[index]
def setval(self, index, c):
"change the value of the MutableString object at index to c"
self.string = self.string[:index] + c + self.string[index+1:]
def reverse(self):
"change the value of the MutableString object to its reverse"
self.string = self.string[::-1]
def __repr__(self):
"specifies how the MutableString object is displayed"
return self.string
s = MutableString('deer')
print 's is:', s
print 's.length() is:', s.length()
print 's.getval(3) is:', s.getval(3)
s.reverse()
print 'after s.reverse(), s is:', s
s.setval(0, 's')
print "after s.setval(0, 's'), s is:", s
s
Just like the inheritance hierarchies of data types in Python, or classes in cs1graphics, we can define our own classes that inherit from others.
Here is a class called Animal
. Animals generally make noises and walk.
class Animal:
def __init__(self, animalName, animalSpecies, animalNoise):
self.name = animalName
self.species = animalSpecies
self.noise = animalNoise
def makeNoise(self):
print 'This', self.species, 'named', self.name, 'says', self.noise
def walk(self):
print 'This', self.species, 'named', self.name, 'has gone for a walk'
snoopy = Animal('Snoopy', 'dog', 'woof')
garfield = Animal('Garfield', 'cat', 'miaow')
snoopy.makeNoise()
garfield.makeNoise()
snoopy.walk()
And here is a class called Bird
. Bird
is just a specific type of animal, so it inherits all its states and behaviors from Animal
.
Birds can fly, save for a few cases.
class Bird(Animal): # A Bird inherits all Animal methods plus can define extra ones
def canFly(self): # can this bird fly?
return self.species not in ['penguin', 'ostrich', 'kiwi']
tuxedo = Bird('Tuxedo', 'penguin', 'honk')
flit = Bird('Flit', 'hummingbird', 'buzz')
flit.makeNoise()
flit.walk()
print 'Can', tuxedo.name, 'fly?', tuxedo.canFly()
print 'Can', flit.name, 'fly?', flit.canFly()
We can define Fish
that inherits from Animal
as well.
But fish can't walk! So we shall over-ride the walk
method of Animal
. Note that we cannot delete the method, only change what it does.
class Fish(Animal):
def walk(self): # Override the walk method inherited from Animal
print 'Silly!', self.species, 'cannot walk.'
nemo = Fish('Nemo', 'clownfish', 'glub, gurgle')
nemo.makeNoise()
nemo.walk()
This does not change the walk
method of Animal
or Bird
.
simba = Animal('Simba', 'lion', 'roar')
simba.makeNoise()
simba.walk()
Insect
¶Define a new class Insect
that inherits from Animal
. In Insect
override the definition of the method walk
.
class Insect(Animal):
def walk(self):
"Override the walk method inherited from Animal"
if self.species.lower()=='butterfly':
print 'This', self.species, 'named', self.name, 'flies in the air'
else:
print 'This', self.species, 'named', self.name, 'crawls'
monarch = Insect('Monarch', 'butterfly', 'click click')
monarch.walk()
simba.walk() # This doesn't change the walk method of Animal or Bird
What if we wanted our constructor to print a message that we created a Bird
object?
To do this, we need to override the __init__
method for Bird
. Since we want it to do the same thing as the Animal
constructor, but with an additional printed message, we can just invoke the constructor of the Animal
class.
class Bird(Animal):
def __init__(self, animalName, animalSpecies, animalNoise):
Animal.__init__(self, animalName, animalSpecies, animalNoise)
print 'You just created a Bird object for a', self.species
def canFly(self): # can this bird fly?
return self.species not in ['penguin', 'ostrich', 'kiwi']
tweety = Bird('Tweety', 'canary', 'tweet')
tweety.makeNoise()
print 'Can', tweety.name, 'fly?', tweety.canFly()
Some birds can talk as well as make noises.
class TalkingBird(Bird):
def talk(self):
print 'Arg. Polly want a cracker. Arg.'
sir_pecks_a_lot = TalkingBird('Sir-Pecks-A-Lot', 'parrot', 'squack')
sir_pecks_a_lot.makeNoise()
sir_pecks_a_lot.walk()
sir_pecks_a_lot.talk()
What will happen with the line below?
tweety.talk()
class Human:
def __init__(self, name):
self.name = name
print 'Hello, my name is', self.name
def exhibitWeakness(self):
print 'mortality'
class Vampire(Human):
# vampires are humans but aren't mortal, so we should over-ride their weakness.
# just for fun, we'll also say that their weakness is not the same as a general human's
def exhibitWeakness(self):
print 'not', # comma at end of print statement avoids a new line
Human.exhibitWeakness(self)
print 'but a stake through the heart!'
class Superhero(Human):
# superheros are humans with more abilities
def fly(self):
print 'wheeee, I am saving the world!'
# they are also not mortal
def exhibitWeakness(self):
print 'not',
Human.exhibitWeakness(self)
print 'but Kryptonite.'
bieber = Human('Justin Bieber')
print 'My weakness is',
bieber.exhibitWeakness()
dracula = Vampire('Count Dracula')
print 'My weakness is',
dracula.exhibitWeakness()
kent = Superhero('Clark Kent')
kent.fly()
print 'My weakness is',
kent.exhibitWeakness()
bieber.fly()
Define the following class hierarchy. What instance variable and methods should be common to all classes?
#Fill in your code for Shape
class Shape:
def __init__(self, width, height):
self.width = width
self.height = height
self.description = "This shape is undefined"
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * self.width + 2 * self.height
def describe(self, text):
self.description=text
def __repr__(self):
return "<{d} with width: {w}, and height: {h}>".format(d = self.description,
w = self.width,
h = self.height)
#Fill in your code for Rectangle
class Rectangle(Shape):
def __init__(self,width,height):
Shape.__init__(self,width,height)
self.description = "This is a rectangle"
#Fill in your code for Triangle
#Fill in your code for Triangle
class Triangle(Shape):
def __init__(self,base,side_a,side_c,height,):
Shape.__init__(self,base,height)
self.side_a=side_a
self.side_c=side_c
self.description = "This is a triangle"
def area(self):
return (self.width * self.height)/2.0
def perimeter(self):
return self.width + self.side_a + self.side_b
def describe(self,text):
self.description=text
def __repr__(self):
return self.description + " with side a: " + str(self.side_a) + ", side b: " + str(self.width) + ", side c: " + str(self.side_c)
# notice how much work you have to do with string concatenation
# can you use the format method instead?
#Fill in your code for Circle
import math
class Circle(Shape):
def __init__(self,radius):
Shape.__init__(self,0,0)
self.radius=radius
self.description="This is a circle"
def area(self):
return math.pi*self.radius*self.radius
def perimeter(self):
return 2*math.pi*self.radius
def describe(self,text):
self.description=text
def __repr__(self):
return self.description + " with a radius of: " + str(self.radius)
#Test your classes
s = Shape(20,30)
print s
r = Rectangle(20,30)
print r, ", area= " + str(r.area())
t = Triangle(12,10,10,8)
print t, ", area= " + str(t.area())
c = Circle(10)
print c, ", area= " + str(c.area())
from turtle import *
class RegularPolygon:
"""polygons where all sides have the same length"""
def __init__(self, numSides, sideLen):
self.numSides = numSides
self.sideLen = sideLen
self.angle = 360.0/numSides # store angle as instance variable, because it's useful
def drawShape(self):
for i in range(self.numSides):
fd(self.sideLen)
lt(self.angle)
exitonclick()
def getPerimeter(self):
return self.sideLen * self.numSides
def printInfo(self):
print 'I have', self.numSides, 'sides, each with length', self.sideLen
print 'My angle is', self.angle
print 'My perimeter is', self.getPerimeter()
def test(self):
setup(600, 600, 0, 0)
self.drawShape()
self.printInfo()
class Square(RegularPolygon):
def __init__(self, sideLen):
RegularPolygon.__init__(self, 4, sideLen) # invoke special case of polygon constructor
def getArea(self):
return self.sideLen * self.sideLen
def printInfo(self):
RegularPolygon.printInfo(self)
print 'My area is', self.getArea()
class ColoredSquare(Square):
def __init__(self, sideLen, color):
Square.__init__(self, sideLen)
self.color = color
def drawShape(self): # over-riding drawShape from Polygon.
pencolor(self.color)
Square.drawShape(self)
p = RegularPolygon(6, 100)
p.test()
s = Square(200)
s.test()
c = ColoredSquare(200, 'red')
c.test() # what version of drawShape will showInfo use? The one with or without the color?