You are on page 1of 51

Introduction to Computing Using Python

Object-Oriented Programming

 Defining new Python Classes


 Container Classes
 Overloaded Operators
 Inheritance
 User-Defined Exceptions
Introduction to Computing Using Python

A new class: Point


Suppose we would like to have a class that represents points on a plane
• for a graphics app, say

Let’s first informally describe how we would like to use this class

>>> point = Point() Usage Explanation point


>>> point.setx(3) y
>>> point.sety(4) p.setx(xcoord) Sets the x coordinate of point p to
>>> point.get() xcoord
(3, 4)
>>> point.move(1, 2) p.sety(ycoord) Sets the y coordinate of point p to
>>> point.get() ycoord
(4, 6)
>>> point.setx(-1) p.get() Returns the x and y coordinates
x of
>>> point.get() point p as a tuple (x, y)
(-1, 6)
>>> p.move(dx, dy) Changes the coordinates of point p
from the current (x, y) to (x+dx, y+dy)

How do we create this new class Point?


Introduction to Computing Using Python

A class is a namespace (REVIEW)


A class is really a namespace >>> list.pop
<method 'pop' of 'list' objects>
>>> list.sort
• The name of this namespace is the name <method 'sort' of 'list' objects>
of the class >>> dir(list)
['__add__', '__class__',
• The names defined in this namespace ...
are the class attributes (e.g., class 'index', 'insert', 'pop', 'remove',
methods) 'reverse', 'sort']
• The class attributes can be accessed
using the standard namespace notation Function dir() can be used to list
the class attributes

__add__ count pop x


. . .
namespace list

__add__() count() pop() sort()


Introduction to Computing Using Python

>>> lst = [9, 1, 8, 2, 7, 3]


Class methods (REVIEW) >>>
[9,
lst
1, 8, 2, 7, 3]
>>> lst.sort()
>>> lst
A class method is really a function defined in the [1, 2, 3, 7, 8, 9]
>>> lst = [9, 1, 8, 2, 7, 3]
class namespace; when Python executes >>> lst
[9, 1, 8, 2, 7, 3]
instance.method(arg1, arg2, …)
lst.sort()
lst.append(6) >>> list.sort(lst)
>>> lst
it first translates it to [1, 2, 3, 7, 8, 9]
>>> lst.append(6)
class.method(instance,
list.sort(lst)
list.append(lst, 6) arg1, arg2, …) >>> lst
[1, 2, 3, 7, 8, 9, 6]
and actually executes this last statement >>> list.append(lst, 5)
>>> lst
[1, 2, 3, 7, 8, 9, 6, 5]
The function has
__add__ count pop x
an extra argument,
which is the object . . .
invoking the method namespace list

__add__() count() pop() sort()


Introduction to Computing Using Python

Developing the class Point


Usage Explanation
p.setx(xcoord) Sets the x coordinate of point p to
A namespace called
xcoord
Point needs to be
p.sety(ycoord) Sets the y coordinate of point p to
defined
ycoord
p.get() Returns the x and y coordinates of
Namespace Point will point p as a tuple (x, y)
store the names of the 4
p.move(dx, dy) Changes the coordinates of point p
methods (the class from the current (x, y) to (x+dx, y+dy)
attributes)

setx sety get move


. . .
namespace Point

setx() sety() get() move()


Introduction to Computing Using Python

Defining the class Point


Usage Explanation

A namespace called p.setx(xcoord)


setx(p, xcoord) Sets the x coordinate of point p to
xcoord
Point needs to be
defined p.sety(ycoord)
sety(p, ycoord) Sets the y coordinate of point p to
ycoord
Namespace Point will p.get()
get(p) Returns the x and y coordinates of
store the names of the 4 point p as a tuple (x, y)
methods (the class p.move(dx,
move(p, dx,dy)
dy) Changes the coordinates of point p
attributes) from the current (x, y) to (x+dx, y+dy)
y+dy)
Each method is a >>> Point.get(point)
(-1, 6)
function that has an >>> Point.setx(point, 0)
extra (first) argument >>> Point.get(point)
(0, 6)
which refers to the >>> Point.sety(point, 0)
object that the method >>> Point.get(point)
(0, 0)
is invoked on >>> Point.move(point, 2, -2)
>>> Point.get(point)
(2, -2)
Introduction to Computing Using Python

Defining the class Point variable that refers to the object


on which the method is invoked

A namespace called class Point:


'class that represents a point in the plane'
Point needs to be
defined def setx(self, xcoord):
'set x coordinate of point to xcoord'
# to be implemented
Namespace Point will
def sety(self, ycoord):
store the names of the 4 'set y coordinate of point to ycoord'
methods (the class # to be implemented
attributes)
def get(self):
'return coordinates of the point as a tuple'
Each method is a # to be implemented
function that has an def move(self, dx, dy):
extra (first) argument 'change the x and y coordinates by dx and dy'
# to be implemented
which refers to the
object that the method
is invoked on

The Python class statement defines a new class (and associated namespace)
Introduction to Computing Using Python

The object namespace

We know that a class Point:


'class that represents a point in the plane'
namespace is associated
with every class def setx(self, xcoord):
'set x coordinate of point to xcoord'
A namespace is also # to be=implemented
self.x xcoord
associated with every def sety(self, ycoord):
object 'set y coordinate of point to ycoord'
# to be implemented
>>> point = Point()
>>> Point.setx(point,
point.x = 3 3)
def get(self):
>>>
'return coordinates of the point as a tuple'
# to be implemented
x
def move(self, dx, dy):
'change the x and y coordinates by dx and dy'
# to be implemented
namespace point

The Python class statement defines a new class


3
Introduction to Computing Using Python

Defining the class Point

A namespace called class Point:


'class that represents a point in the plane'
Point needs to be
defined def setx(self, xcoord):
'set x coordinate of point to xcoord'
self.x = xcoord
Namespace Point will
def sety(self, ycoord):
store the names of the 4 'set y coordinate of point to ycoord'
methods (the class self.y = ycoord
attributes)
def get(self):
'return coordinates of the point as a tuple'
Each method is a return (self.x, self.y)
function that has an def move(self, dx, dy):
extra (first) argument 'change the x and y coordinates by dx and dy'
self.x += dx
which refers to the self.y += dy
object that the method
is invoked on
Introduction to Computing Using Python

Exercise

Add new method getx() class Point:


'class that represents a point in the plane'
to class Point
def setx(self, xcoord):
'set x coordinate of point to xcoord'
self.x = xcoord
>>> point = Point()
>>> point.setx(3) def sety(self, ycoord):
>>> point.getx() 'set y coordinate of point to ycoord'
3 self.y = ycoord

def get(self):
'return coordinates of the point as a tuple'
return (self.x, self.y)

def move(self, dx, dy):


'change the x and y coordinates by dx and dy'
self.x += dx
self.y += dy

def getx(self):
'return x coordinate of the point'
return self.x
Introduction to Computing Using Python

The instance namespaces


Variables stored in the namespace of an object (instance) are called
instance variables (or instance attributes)

Every object will have its own namespace and therefore its own
instance variables
>>> a = Point()
>>> a.setx(3)
>>> a.sety(4) x y x y
>>> b = Point()
>>> b.setx(0)
>>> b.sety(0)
>>> a.get() object a object b
(3, 4)
>>> b.get()
(0, 0)
>>> a.x 3 4 0 0
3
>>> b.x
0
>>>
Introduction to Computing Using Python

The class and instance attributes

An instance of a class inherits setx sety get move


all the class attributes . . .
namespace Point
>>> dir(a)
['__class__', '__delattr__', ...
'__dict__', '__doc__',
'__eq__', '__format__',
'__ge__', '__getattribute__',
x y x y
'__gt__', '__hash__',
'__init__', '__le__',
'__lt__', '__module__', object a object b
'__ne__', '__new__',
'__reduce__', '__reduce_ex__',
'__repr__', '__setattr__',
'__sizeof__', '__str__',
'__subclasshook__',
3 4 0 0
'__weakref__', 'get', 'move',
'setx', 'sety', 'x', 'y’]

Function dir() returns the attributes of


class Point attributes
instance
inherited by a of a
attributes an object, including the inherited ones
Introduction to Computing Using Python

The class and instance attributes

Method names setx, sety, setx sety get move


get, and move are defined in . . .
namespace Point
namespace Point
• not in namespace a or b.
...
Python does the following
when evaluating expression x y x y
a.setx:
object a object b
1. It first attempts to find
name setx in object
(namespace) a.
3 4 0 0
2. If name setx does not
exist in namespace a, then
it attempts to find setx in
namespace Point
Introduction to Computing Using Python

Class definition, in general


class Point:
<Class Name>:
<class variable 1> = <value>
<class
def setx(self,
variablexcoord):
2> = <value>
... self.x = xcoord
def <class method 1>(self, arg11, arg12, ...):
def <implementation
sety(self, ycoord):
of class method 1>
self.y = ycoord
def <class method 2>(self, arg21, arg22, ...):
def <implementation
get(self): of class method 2>
return (self.x, self.y)
...
def move(self, dx, dy):
self.x += dx
self.y += dy

Note: no documentation
Introduction to Computing Using Python

(No) class documentation


>>> help(Point)
Help on class Point in module __main__:

class Point(builtins.object)
| Methods defined here:
|
| get(self)
|
| move(self, dx, dy)
|
| setx(self, xcoord)
|
| sety(self, ycoord)
|
|
----------------------------------------------------------------------
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
Introduction to Computing Using Python

Class documentation
class Point:
'class that represents a point in the plane'

def setx(self, xcoord):


'set x coordinate of point to xcoord'
self.x = xcoord

def sety(self, ycoord):


'set y coordinate of point to ycoord'
self.y = ycoord

def get(self):
'return coordinates of the point as a tuple'
return (self.x, self.y)

def move(self, dx, dy):


'change the x and y coordinates by dx and dy'
self.x += dx
self.y += dy
Introduction to Computing Using Python

Class documentation
>>> help(Point)
Help on class Point in module __main__:

class Point(builtins.object)
| class that represents a point in the plane
|
| Methods defined here:
|
| get(self)
| return a tuple with x and y coordinates of the point
|
| move(self, dx, dy)
| change the x and y coordinates by dx and dy
|
| setx(self, xcoord)
| set x coordinate of point to xcoord
|
| sety(self, ycoord)
| set y coordinate of point to ycoord
|
|
----------------------------------------------------------------------
| Data descriptors defined here:
...
Introduction to Computing Using Python

Exercise
Develop class Animal that supports methods: >>> snoopy = Animal()
• setSpecies(species) >>> snoopy.setpecies('dog')
>>> snoopy.setLanguage('bark')
• setLanguage(language) >>> snoopy.speak()
• speak() I am a dog and I bark.

class Animal:
'represents an animal'

def setSpecies(self, species):


'sets the animal species'
self.spec = species

def setLanguage(self, language):


'sets the animal language'
self.lang = language

def speak(self):
'prints a sentence by the animal'
print('I am a {} and I {}.'.format(self.spec, self.lang))
Introduction to Computing Using Python

Overloaded constructor
called by Python each time a Point object is created

class Point:
It takes 3 steps to create a 'class that represents a point in the plane’
Point object at specific x setx(self, xcoord):
__init__(self,
defdef xcoord, ycoord):
and y coordinates 'set x coordinate
'initialize of point
coordinates to xcoord'
to (xcoord, ycoord)'
self.x = xcoord
>>> a = Point() self.y = ycoord
def sety(self, ycoord):
It would
>>> be better if we
a.setx(3)
'set y coordinate
def setx(self, of point to ycoord'
xcoord):
>>> a.sety(4)
could do it in one step
>>> a.get()
self.y
'set = ycoord
x coordinate of point to xcoord'
(3, 4) self.x = xcoord
def get(self):
>>>
'return coordinates
def sety(self, ycoord): of the point as a tuple'
return
'set (self.x, self.y)
y coordinate of point to ycoord'
>>> a = Point(3, 4)
self.y = ycoord
>>> a.get() def move(self, dx, dy):
(3, 4) 'change the x and y coordinates by dx and dy'
def get(self):
>>> self.x += dx
'return coordinates of the point as a tuple'
self.y (self.x,
return += dy self.y)

def move(self, dx, dy):


'change the x and y coordinates by dx and dy'
self.x += dx
self.y += dy
Introduction to Computing Using Python

Default constructorxcoord is set to 0 if the argument is missing


ycoord is set to 0 if the argument is missing
class Point:
Problem: Now we can’t 'class that represents a point in the plane’
create an uninitialized point
def __init__(self, xcoord=0,
xcoord, ycoord):
ycoord=0):
'initialize coordinates to (xcoord, ycoord)'
Built-in types support self.x = xcoord
default constructors self.y = ycoord

def setx(self, xcoord):


>>> a = Point()
Want to augment class 'set x coordinate of point to xcoord'
Traceback (most recent call last): self.x = xcoord
Point so it supports aline 1, in <module>
File "<pyshell#0>",
a = constructor
default Point() def sety(self, ycoord):
TypeError: __init__() takes exactly 3'set
arguments (1 given)
y coordinate of point to ycoord'
>>> self.y = ycoord

>>> a
n = Point()
int(3) def get(self):
>>> a.get()
n 'return coordinates of the point as a tuple'
(0,
3 0) return (self.x, self.y)
>>> n = int()
>>> n def move(self, dx, dy):
0 'change the x and y coordinates by dx and dy'
>>> self.x += dx
self.y += dy
Introduction to Computing Using Python

Exercise
Modify the class Animal we developed in the previous section so it supports a
two, one, or no input argument constructor

>>> snoopy = Animal('dog', 'bark')


>>> snoopy.speak()
class Animal:
I am a dog and I bark.
'represents an animal'
>>> tweety = Animal('canary')
>>> tweety.speak()
def __init__(self, species='animal', language='make sounds'):
I am a canary and I make sounds.
self.species = species
>>> animal = Animal()
self.language = language
>>> animal.speak()
I am a animal and I make sounds.
def setSpecies(self, species):
'sets the animal species'
self.spec = species

def setLanguage(self, language):


'sets the animal language'
self.lang = language

def speak(self):
'prints a sentence by the animal'
print('I am a {} and I {}.'.format(self.spec, self.lang))
Introduction to Computing Using Python

Example: class Card

Goal: develop a class Card class to represent playing cards.


The class Card should support methods:
• Card(rank, suit): Constructor that initializes the rank and suit of the card
• getRank(): Returns the card’s rank
• getSuit(): Returns the card’s suit >>> card = Card('3', '\u2660')
>>> card.getRank()
class Card: '3'
'represents a playing card' >>> card.getSuit()
'♠'
def __init__(self, rank, suit): >>>
'initialize rank and suit of card'
self.rank = rank
self.suit = suit

def getRank(self):
'return rank'
return self.rank

def getSuit(self):
'return suit'
return self.suit
Introduction to Computing Using Python

Container class: class Deck

Goal: develop a class Deck to represent a standard deck of 52 playing cards.


The class Deck should support methods:
• Deck(): Initializes the deck to contain a standard deck of 52 playing cards
• shuffle(): Shuffles the deck
• dealCard(): Pops and returns the card at the top of the deck

>>> deck = Deck()


>>> deck.shuffle()
>>> card = deck.dealCard()
>>> card.getRank(), card.getSuit()
('2', '♠')
>>> card = deck.dealCard()
>>> card.getRank(), card.getSuit()
('Q', '♣')
>>> card = deck.dealCard()
>>> card.getRank(), card.getSuit()
('4', '♢')
>>>
Introduction to Computing Using Python

Container class: class Deck


class Deck:
'represents a deck of 52 cards'

# ranks and suits are Deck class variables


ranks = {'2','3','4','5','6','7','8','9','10','J','Q','K','A'}

# suits is a set of 4 Unicode symbols representing the 4 suits


suits = {'\u2660', '\u2661', '\u2662', '\u2663'}

def __init__(self):
'initialize deck of 52 cards'
self.deck = [] # deck is initially empty

for suit in Deck.suits: # suits and ranks are Deck


for rank in Deck.ranks: # class variables
# add Card with given rank and suit to deck
self.deck.append(Card(rank,suit))

def dealCard(self):
'deal (pop and return) card from the top of the deck'
return self.deck.pop()

def shuffle(self):
'shuffle the deck'
shuffle(self.deck)
Introduction to Computing Using Python

Container class: class Queue

Goal: develop a class Queue , an ordered collection of objects that restricts


insertions to the rear of the queue and removal from the front of the queue
The class Queue should support methods:
• Queue(): Constructor that initializes the queue to an empty queue
• enqueue(): Add item to the end of the queue
• dequeue(): Remove and return the element at the front of the queue
• isEmpty(): Returns True if the queue is empty, False otherwise

>>> appts = Queue()


>>> appts.enqueue('John')
>>> appts.enqueue('Annie')
>>> appts.enqueue('Sandy')
>>> appts.dequeue()
'John'
>>> appts.dequeue()
'Annie'
>>> appts.dequeue()
'Sandy'
>>> appts.isEmpty()
True
Introduction to Computing Using Python

Container class: class Queue

rear
rear rear
rear rear
appts 'Annie' 'Annie'
'John' 'Sandy' 'Sandy'
front

>>> appts = Queue()


>>> appts.enqueue('John')
>>> appts.enqueue('Annie')
>>> appts.enqueue('Sandy')
>>> appts.dequeue()
'John'
>>> appts.dequeue()
'Annie'
>>> appts.dequeue()
'Sandy'
>>> appts.isEmpty()
True
Introduction to Computing Using Python

Container class: class Queue

class Queue:
'a classic queue class'

def __init__(self):
'instantiates an empty list'
self.q = []

def isEmpty(self):
'returns True if queue is empty, False otherwise'
return (len(self.q) == 0)

def enqueue (self, item):


'insert item at rear of queue'
return self.q.append(item)

def dequeue(self):
'remove and return item at front of queue'
return self.q.pop(0)
Introduction to Computing Using Python

Our classes are not user-friendly


>>> a = Point(3, 4) >>> a = Point(3, 4)
>>> a >>> a
<__main__.Point object at 0x10278a690> Point(3, 4)

>>> a = Point(3,4) >>> a = Point(3,4)


>>> b = Point(1,2) >>> b = Point(1,2)
>>> a+b >>> a+b
Traceback (most recent call last): Point(4, 6)
File "<pyshell#44>", line 1, in <module>
a+b
TypeError: unsupported operand type(s) for
(xWhat
and ywould
coordinates are
we prefer?
+: 'Point' and 'Point' added, respectively)
>>> appts = Queue() >>> appts = Queue()
>>> len(appts) >>> len(appts)
Traceback (most recent call last): 0
File "<pyshell#40>", line 1, in <module>
len(appts)
TypeError: object of type 'Queue' has no len()

>>> deck = Deck() >>> deck = Deck()


>>> deck.shuffle() >>> deck.shuffle()
>>> deck.dealCard() >>> deck.dealCard()
<__main__.Card object at 0x10278ab90> Card('2', '♠’)
Introduction to Computing Using Python

Python operators
>>> 'he' + 'llo' >>> 'he'.__add__('llo')
'hello' 'hello'
>>> [1,2] + [3,4] >>> [1,2].__add__([3,4])
[1, 2, 3, 4] [1, 2, 3, 4]
>>> 2+4 >>> int(2).__add__(4)
6 6

Operator + is defined for multiple classes; it is an overloaded operator.


• For each class, the definition—and thus the meaning—of the operator
is different.
o integer addition for class int
o list concatenation for class list

o string concatenation for class str

• How is the behavior of operator + defined for a particular class?

Class method __add__() implements the behavior of operator + for the class
… it first translates it to … and then evaluates
When Python evaluates
method invocation … the method invocation
object1 + object 2 object1.__add__(object2)
Introduction to Computing Using Python
Operator Method

Python operators x + y x.__add__(y)


x – y x.__sub__(y)
x * y x.__mul__(y)
x / y x.__truediv__(y)
In Python, all expressions involving
operators are translated into method calls x // y x.__floordiv__(y)

• (Recall that method invocations are x % y x.__mod__(y)


>>>'!'.__mul__(10)
>> '!'*10
then further translated to function
'!!!!!!!!!!' x == y x.__eq__(y)
calls ==
in a[2,3,4]
namespace)
>>> [1,2,3].__eq__([2,3,4])
[1,2,3]
False x != y x.__ne__(y)
>>> int(2).__lt__(5)
2 < 5
Built-in
True function repr() returns the x > y x.__gt__(y)
canonical
'a' <=string
'a' representation of an object
>>> 'a'.__le__('a')
x >= y x.__ge__(y)
True
• This is the representation printed by
>>> [1,1,2,3,5,8].__len__()
len([1,1,2,3,5,8]) x < y x.__lt__(y)
6 the shell when evaluating the object
x <= y x.__le__(y)
>>> [1,2,3].__repr__()
repr([1,2,3])
[1,2,3] repr(x) x.__repr__()
[1, 2,
'[1, 2,3]
3]'
>>> int(193).__repr__()
repr(193)
193 str(x) x.__str__()
'193'
193
'193’
>>> set().__repr__()
repr(set())
set() len(x) x.__len__()
set()
'set()'
<type>(x) <type>.__init__(x)
Introduction to Computing Using Python

Overloading repr()

In Python, operators are translated into method calls

To add an overloaded operator to a user-defined class, the


corresponding method must be implemented
>>> a = Point(3, 4) >>> a = Point(3, 4)
To get this behavior >>> a >>> a.__repr__()
Point(3, 4) Point(3, 4)

method __repr__() must be implemented and added to class Point

__repr__() should return the (canonical) string representation of the point

class Point:

# other Point methods here

def __repr__(self):
'canonical string representation Point(x, y)'
return 'Point({}, {})'.format(self.x, self.y)
Introduction to Computing Using Python

Overloading operator +
>>> a = Point(3,4) >>> a = Point(3,4)
To get this behavior >>> b = Point(1,2) >>> b = Point(1,2)
>>> a+b >>> a.__add__(b)
Point(4, 6) Point(4, 6)

method __add__() must be implemented and added to class Point


__add__() should return a new Point object whose coordinates are the
sum of the coordinates of a and b
Also, method __repr__() should be implemented to achieve the desired
display of the result in the shell
class Point:

# other Point methods here

def __add__(self, point):


return Point(self.x+point.x, self.y+point.y)

def __repr__(self):
'canonical string representation Point(x, y)'
return 'Point({}, {})'.format(self.x, self.y)
Introduction to Computing Using Python

Overloading operator len()


>>> appts = Queue() >>> appts = Queue()
To get this behavior >>> len(appts) >>> appts.__len__()
0 0

method __len__() must be implemented and added to class Queue

__len__() should return the number of objects in the queue


• i.e., the size of list self.q
class Queue:
def __init__(self):
self.q = []

def isEmpty(self):
return (len(self.q) == 0)

def enqueue (self, item):


We use the fact that len() return self.q.append(item)
is implemented for class list
def dequeue(self):
return self.q.pop(0)

def __len__(self):
return len(self.q)
Introduction to Computing Using Python

Exercise
Modify Deck >>> deck = Deck()
>>> deck.shuffle()
and/or Card to >>> deck.dealCard()
get this behavior Card('2', '♠')

class Card:
'represents a playing card'

def __init__(self, rank, suit):


'initialize
class Card: rankDeck:
class and suit of card'
self.rank
'represents a = rank
playing
rankscard'
= {'2','3','4','5','6','7','8','9','10','J','Q','K','A’}
self.suit = suitsuits = {'\u2660', '\u2661', '\u2662', '\u2663’}
def __init__(self,def rank,
__init__(self):
suit):
def getRank(self):
'initialize rank and 'initialize
suit of card'
deck of 52 cards'
'return
self.rankrank'
= rank self.deck = [] # deck is initially empty
return self.rank
self.suit = suit for suit in Deck.suits: # suits and ranks are Deck
for rank in Deck.ranks: # class variables
def getSuit(self):
getRank(self): self.deck.append(Card(rank,suit))
'return suit'
rank'
return self.suit
self.rank
def dealCard(self):
return self.deck.pop()
def def
__repr__(self):
getSuit(self):
'return formal representation'
suit' def shuffle(self):
return "Card('{}', '{}')".format(self.rank, self.suit)
self.suit shuffle(self.deck)
Introduction to Computing Using Python
Operator Method

str() vs repr() x + y x.__add__(y)


x – y x.__sub__(y)
x * y x.__mul__(y)
x / y x.__truediv__(y)
Built-in function repr() returns the
canonical string representation of an object x // y x.__floordiv__(y)
• This is the representation printed by x % y x.__mod__(y)
the shell when evaluating the object
x == y x.__eq__(y)
x != y x.__ne__(y)
Built-in function str() returns the “pretty” x > y x.__gt__(y)
string representation of an object x >= y x.__ge__(y)
• This is the representation printed by
x < y x.__lt__(y)
the print() statement and is meant
to be readable by humans x <= y x.__le__(y)
>>> str([1,2,3])
print([1,2,3])
[1,2,3].__str__() repr(x) x.__repr__()
[1, 2,
'[1, 2,3]
3]'
>>> str(193)
print(193)
int(193).__str__() str(x) x.__str__()
193
'193’
'193'
>>> str(set())
print(set())
set().__str__() len(x) x.__len__()
set()
'set()'
<type>(x) <type>.__init__(x)
Introduction to Computing Using Python

str() vs repr()

Built-in function repr() returns the


canonical string representation of an object
• This is the representation printed by
the shell when evaluating the object >>> r = Representation()
>>> r
canonical string representation
>>> print(r)
Built-in function str() returns the “pretty” Pretty string representation.
>>>
string representation of an object
• This is the representation printed by
the print() statement and is meant
to be readable by humans

class Representation(object):
def __repr__(self):
return 'canonical string representation'
def __str__(self):
return 'Pretty string representation.'
Introduction to Computing Using Python

Canonical string representation

Built-in function repr() returns the >>> repr([1,2,3])


'[1, 2, 3]’
3]'
canonical string representation of an object >>> [1,2,3]
[1, 2, 3]
• This is the representation printed by >>> eval(repr([1,2,3]))
the shell when evaluating the object [1, 2, 3]
>>> [1,2,3] == eval(repr([1,2,3]))
• Ideally, this is also the string used to True
construct the object
o e.g., '[1, 2, 3]' , 'Point(3, 5)'
• In other words, the expression
eval(repr(o))
should give back an object equal to the Problem: operator ==
original object o
>>> repr(Point(3,5))
'Point(3, 5)'


>>> eval(repr(Point(3,5)))
Contract between the constructor Point(3, 5)
>>> Point(3,5) == eval(repr(Point(3,5)))
and operator repr() False
Introduction to Computing Using Python

Overloading operator ==
>>> a = Point(3,5)
For user-defined classes, the >>> b = Point(3,5)
>>> a == b
default behavior for operator == False
is to return True only when the >>> a == a
two objects are the same object. True

Usually, that is not the desired behavior


• It also gets in the way of satisfying the contract between constructor and repr()

For class Point, operator == should return True if the two points have the
same coordinates
class Point:

# other Point methods here

contract between def __eq__(self, other):


constructor and 'self == other if they have the same coordinates'
return self.x == other.x and self.y == other.y
repr() is now satisfied
def __repr__(self):
'return canonical string representation Point(x, y)'
return 'Point({}, {})'.format(self.x, self.y)`
Introduction to Computing Using Python

Exercise
We have already modified class Card to support function repr(). Now
implement
class Card: operator == so two cards with same rank and suit are equal.
'represents a playing card'

def __init__(self, rank, suit):


'initialize rank and suit of card'
class Card: >>> card1 =
self.rank
'represents = rank card'
a playing Card('4', '\u2662')
self.suit = suit >>> card2 =
def __init__(self, rank, suit): Card('4', '\u2662')
def
class getRank(self):
'initialize rank and suit of card'
Card: >>> card1 == card2
'return
self.rank
'represents rank'
a = rank card'
playing True
return self.rank
self.suit = suit
def __init__(self, rank, suit):
def getSuit(self):
getRank(self):
'initialize rank and suit of card'
'return
self.ranksuit'
rank'
= rank
return self.suit
self.rank
self.suit = suit

def getSuit(self):
__repr__(self):
getRank(self):
'return formal
rank' representation'
suit'
return "Card('{}',
self.suit
self.rank '{}')".format(self.rank, self.suit)

__eq__(self,
__repr__(self):
defdef getSuit(self):other):
'self ==formal
'return other if
suit' rank and suit are the same'
representation'
return self.rank
self.suit =='{}')".format(self.rank,
"Card('{}', other.rank and self.suitself.suit)
== other.suit
Introduction to Computing Using Python

Inheritance
Code reuse is a key software engineering goal
• One benefit of functions is they make it easier to reuse code
• Similarly, organizing code into user-defined classes makes it easier to later
reuse the code
o E.g., classes Card and Deck can be >>> mylst = MyList()
reused in different card game apps >>> mylst.append(2)
>>> mylst.append(3)
>>> mylst.append(5) A MyList
A class can also be reused by extending >>> mylst.append(7) object should
>>> len(mylst) behave just
it through inheritance 4 like a list
>>> mylst.index(5)
2
Example: Suppose that we find it >>> mylst.choice()
7
convenient to have a class that >>> mylst.choice()
3
A MyList
behaves just like the built-in class object should
>>> mylst.choice()
list but also supports a method 3 also support
called choice() that returns an item >>> mylst.choice() method
5 choice()
from the list, chosen uniformly at >>> mylst.choice()
random. 3
Introduction to Computing Using Python

Implementing class MyList


2: Develop class MyList from
Approach 1: by inheritance
scratch from class list
• Just like classes Deck and Queue
Huge amount of work!
Class MyList inherits all the attributes of class list
import random >>> mylst = MyList()
class MyList(list):
MyList: >>> mylst.append(2)
'a
defsubclass of listinitial
__init__(self, that implements
= []): method choice'
>>> mylst.append(3)
self.lst = initial >>> mylst.append(5) A MyList
def choice(self): >>> mylst.append(7) object should
'return item from list chosen uniformly at>>>
def __len__(self): random'
len(mylst) behave just
return random.choice(self)
len(self.lst) 4 like a list
>>> mylst.index(5)
def append(self, item): 2
self.lst.append(self,
Example: Suppose that we item)
find it >>> mylst.choice()
7
# convenient to have
implementations of a class that"list" methods >>> mylst.choice()
remaining
3
A MyList
behaves just like the built-in class object should
def choice(self): >>> mylst.choice()
list but also supports a method
return random.choice(self.lst) 3 also support
called choice() that returns an item >>> mylst.choice() method
5 choice()
from the list, chosen uniformly at >>> mylst.choice()
random. 3
Introduction to Computing Using Python

Class MyList by inheritance


>>> dir(MyList) __init__ append __len__ index
['__add__', '__class__',
. . . . . .
'choice', 'count', 'extend',
'index', 'insert', 'pop', class list
'remove', 'reverse', 'sort']
>>> dir(mylst)
['__add__', '__class__', Class mylst inherits
ObjectMyList inherits choice
. . . all the attributes of
'choice', 'count', 'extend',
'index', 'insert', 'pop',
class MyList
list (which
'remove', 'reverse', 'sort'] inherits all the class MyList
>>> attributes of class
list)

import random
[2,3,5,7]
class MyList(list): object mylst
'a subclass of list that implements method choice'

def choice(self):
'return item from list chosen uniformly at random'
return random.choice(self)
Introduction to Computing Using Python

Class definition, in general

A class can be defined “from scratch” using: class <Class Name>:

A class can also be derived from another class, through inheritance


class <Class Name>(<Super Class>):

class <Class Name>: is a shorthand for class <Class Name>(object):

object is a built-in class with no attributes; it is the class that all classes
inherit from, directly or indirectly
>>> help(object)
Help on class object in module builtins:

class object
| The most base type

A class can also inherit attributes from more than one superclass
class <Class Name>(<Super Class 1>, <Super Class 2>, …):
Introduction to Computing Using Python

Overriding superclass methods


Sometimes we need to develop a new class that can almost inherit
attributes from an existing class… but not quite.

For example, a class Bird that supports the same methods class Animal
supports (setSpecies(), setLanguage(), and speak()) but with a
different behavior for method speak()

class Animal: >>> snoopy = Animal()


'represents an animal’ >>> snoopy.setSpecies('dog')
>>> snoopy.setLanguage('bark')
def setSpecies(self, species): >>> snoopy.speak()
'sets the animal species' I am a dog and I bark.
self.spec = species >>> tweety = Bird()
>>> tweety.setSpecies('canary')
def setLanguage(self, language): >>> tweety.setLanguage('tweet')
'sets the animal language' >>> tweety.speak()
self.lang = language tweet! tweet! tweet!

def speak(self):
'prints a sentence by the animal'
print('I am a {} and I {}.'.format(self.spec, self.lang))
Introduction to Computing Using Python

Overriding superclass methods


setSpecies speak setLanguage

class Animal
spec lang
Python looks for the definition of an
speak
attribute by starting with the name-
space object
associated
snoopy with object and

class Bird continuing up the class hierarchy.

Animal
method speak() defined in Bird is used
is used
spec lang

>>> snoopy = Animal()


Bird inherits all the attributes of Animal… >>> snoopy.setSpecies('dog')
object tweety
… but then overrides the behavior of method speak() >>> snoopy.setLanguage('bark')
>>> snoopy.speak()
class Bird(Animal): I am a dog and I bark.
'represents a bird' >>> tweety = Bird()
>>> tweety.setSpecies('canary')
def speak(self): >>> tweety.setLanguage('tweet')
'prints bird sounds' >>> tweety.speak()
print('{}! '.format(self.lang) * 3) tweet! tweet! tweet!
Introduction to Computing Using Python

Extending superclass methods

A superclass method can be inherited as-is, overridden, or extended.

class Super:
'a generic class with one method'
def method(self): # the Super method
print('in Super.method')

class Inheritor(Super):
'class that inherits method'
pass

class Replacer(Super):
'class that overrides method'
def method(self):
print('in Replacer.method')

class Extender(Super):
'class that extends method'
def method(self):
print('starting Extender.method')
Super.method(self) # calling Super method
print('ending Extender.method')
Introduction to Computing Using Python

Object-Oriented Programming (OOP)


Code reuse is a key benefit of organizing code into new classes; it is made
possible through abstraction and encapsulation.
Abstraction: The idea that a class object can be manipulated by users through
method invocations alone and without knowledge of the implementation of
these methods.
• Abstraction facilitates software development because the programmer works
with objects abstractly (i.e., through “abstract”, meaningful method names
rather than “concrete”, technical code).

Encapsulation: In order for abstraction to be beneficial, the “concrete” code


and data associated with objects must be encapsulated (i.e., made “invisible”
to the program using the object).
• Encapsulation is achieved thanks to the fact that (1) every class defines a
namespace in which class attributes live, and (2) every object has a
namespace, that inherits the class attributes, in which instance attributes live.

OOP is an approach to programming that achieves modular code through the


use of objects and by structuring code into user-defined classes.
Introduction to Computing Using Python

An encapsulation issue

The current implementation of class Queue does not completely encapsulate


its implementation

>>> queue
>>> queue
= Queue()
= Queue()
>>> queue.dequeue()
>>> queue.dequeue()
Traceback
Traceback
(most(most
recent
recent
call call
last):last): We need to be able to
File File
"<pyshell#76>",
"<pyshell#48>",
line line
1, in1,<module>
in <module>
queue.dequeue()
queue.dequeue()
define user-defined
File File
"/Users/me/ch8.py",
"/Users/me/ch8.py",
line line
120, 93,
in dequeue
in dequeue exceptions
raisereturn
EmptyQueueError('dequeue
self.q.pop(0) from empty queue')
EmptyQueueError:
IndexError: pop
dequeue
from from
emptyempty
list queue But first, we need to
learn how to “force” an
What is the problem? exception to be raised
The user of class Queue should not have to know the implementation
detail that a list stores the items in a Queue object

What should be output instead?


Introduction to Computing Using Python

Raising an exception
>>> while True:
By typing Ctrl-C, a user can pass
force a KeyboardInterrupt
Traceback (most recent call last):
exception to be raised File "<pyshell#53>", line 2, in <module>
pass
KeyboardInterrupt
Any exception can be raised >>> raise ValueError()
Traceback (most recent call last):
within a program with the File "<pyshell#54>", line 1, in <module>
raise statement raise ValueError()
ValueError
• ValueError, like all >>> raise ValueError('Just joking...')
exception types, is a class Traceback (most recent call last):
File "<pyshell#55>", line 1, in <module>
• ValueError() uses the raise ValueError('Just joking...')
default constructor to create ValueError: Just joking...
an exception (object) >>> try:
raise ValueError()
• statement raise switches except:
control flow from normal to print('Caught exception.')
exceptional
• The constructor can take a
Caught exception.
“message” argument to be >>>
stored in the exception object
Introduction to Computing Using Python

User-defined exceptions
Every built-in exception >>>
>>> help(Exception)
class MyError(Exception):
Help pass
on class Exception in module builtins:
type is a subclass of class
Exception. class Exception(BaseException)
>>> raise MyError('Message in a bottle')
| Common base recent
Traceback (most class for all
call non-exit exceptions.
last):
|File "<pyshell#71>", line 1, in <module>
| raise
MethodMyError('Message
resolution order:in a bottle')
A new exception class |
MyError: Exception
Message in a bottle
should be a subclass, either |
>>> BaseException
directly or indirectly, of | object
. . .
Exception.
Introduction to Computing Using Python

Class Queue, revisited


class EmptyQueueError(Exception):
Ourpass
goal was to encapsulate class Queue better:
class Queue:
>>> queue = Queue()
'a >>>
classic queue class'
queue.dequeue()
Traceback (most recent call last):
def __init__(self):
File "<pyshell#76>", line 1, in <module>
'instantiates an empty list'
queue.dequeue()
self.q = []
File "/Users/me/ch8.py", line 120, in dequeue
raise EmptyQueueError('dequeue from empty queue')
defEmptyQueueError:
isEmpty(self): dequeue from empty queue
'returns True if queue is empty, False otherwise'
return (len(self.q) == 0)

To achieve this behavior,


def enqueue we:
(self, item):
'insert item at rear of queue'
return self.q.append(item)
1. Need to create exception class EmptyQueueError
def dequeue(self):
2. Modify
'removeQueue methoditem
and return dequeue so anof
at front EmptyQueueError
queue' exception is
if self.isEmpty():
raised if an attempt to dequeue an empty queue is made
raise EmptyQueueError('dequeue from empty queue')
return self.q.pop(0)

You might also like