MIME-Version: 1.0 Content-Location: file:///C:/90861A50/Week10.htm Content-Transfer-Encoding: quoted-printable Content-Type: text/html; charset="us-ascii" Week 10

Week = 10
Object-oriented Programming and Tables

Indiana University=

Computer Science A202 /= A598

and Informatics I211

 

This week’s success strategy

<= ![if !supportLists]>n&nb= sp;  Learn to read computer material like a scien= tist

<= ![if !supportLists]>q   <= /span>ok to read casually the first time to get a general impression

<= ![if !supportLists]>q   <= /span>but you must go back and reread it until you understand it in detail

<= ![if !supportLists]>q   <= /span>reread until you feel you understand why everything is the way it is

<= ![if !supportLists]>q   <= /span>if you don't understand the why of it, you don't understand the underlying concept, and you won't be able to apply the knowl= edge when you are programming

=  

This = week

<= ![if !supportLists]>n   Review of a solution to assignment 8

<= ![if !supportLists]>n   A couple of dictionary examples from last weeks notes

<= ![if !supportLists]>n   Keyword default value strangeness

<= ![if !supportLists]>n   Introduction to object-oriented programming (OOP)

<= ![if !supportLists]>q   <= /span>essence of OOP

<= ![if !supportLists]>q   <= /span>classes

<= ![if !supportLists]>q   <= /span>methods

<= ![if !supportLists]>q   <= /span>instance variables

<= ![if !supportLists]>q   <= /span>instance creation

<= ![if !supportLists]>n   Table class

 =

 =

function

<= ![if !supportLists]>n    Define the function makeDictionary, which t= akes a sequence of (key, value) pairs and returns the corresponding dictionary

>>> items =3D dict.items()

>>> items

[('Apr', 30), ('Mar', 31), ('Feb', 29)]

>>> makeDictionary(items)

{'Apr': 30, 'Mar': 31, 'Feb': 29}

<= ![if !supportLists]>n    Answer

def makeDictionary(items):

    '''Return= a dictionary with those associations

    indicated= by (key, value) pairs in items list.'''

    dict =3D = {}

    for key, = value in items:

        if key in dict:

        =     raise Exception(key + ' already in dictionary')

        dict[key] =3D value

    return di= ct

 

function

<= ![if !supportLists]>n   Write a function that takes a dictionary and returns a dictionary obtained by reversing the key/value roles in its associations

>>> dict

{'Apr': 30, 'Mar': 31, 'Feb': 29}

>>> invertDictionary(dict)

{29: 'Feb', 30: 'Apr', 31: 'Mar'}

<= ![if !supportLists]>n   Solution

def invertDictionary(dict):

    invertedD= ict =3D {}

    for key in dict:

        if dict[key] in invertedDict:

            raise Exception('dictionary is not 1-to-1')

        invertedDict[ dict[key] ] =3D key

    return invertedDict

Keywo= rd default value strangeness

<= ![if !supportLists]>n    This seems very strange. What's going on?

>>> def strange(data=3D[]):

        data.append('x')=

        return data

>>> strange()

['x']

>>> strange()

['x', 'x']

<= ![if !supportLists]>n    Keyword default value expressions, in this case an empty list, are only evaluated once, when the function definition is evalua= ted

<= ![if !supportLists]>q   <= /span>in this example there is just one default value for data, whi= ch both calls modify

<= ![if !supportLists]>n    Moral: never mutate a keyword default value !

<= ![if !supportLists]>q   <= /span>if necessary, make a copy of the value that can be mutated without strangeness

The essence of object-oriented programming

<= ![if !supportLists]>n   An object is a computation value that can contain both state (da= ta) and behavior (code)

<= ![if !supportLists]>n   Object-oriented programming (OOP) greatly facilitates code reuse<= /o:p>

<= ![if !supportLists]>q   <= /span>code can be used in many more circumstances if the data it manipulates contains knowledge of how it behaves<= /p>

<= ![if !supportLists]>q   <= /span>much of the world (both real and artificial) consi= sts of objects containing state and behavior, which OOP can model more directly=

Stori= ng object state and behavior

<= ![if !supportLists]>n    Variables, called fields, that are part of = an object store its state

<= ![if !supportLists]>q   <= /span>the syntax <object expression>.<field name> may be used to access and assign field values

<= ![if !supportLists]>q   <= /span>as usual for Python variables, fields are created via assignment

<= ![if !supportLists]>n    Every object is associated with a class that was used to create it

<= ![if !supportLists]>q   <= /span>an object is said to be an instance of the class that created= it

<= ![if !supportLists]>q   <= /span>think of a class as a factory or blueprint for creating objects=

<= ![if !supportLists]>q   <= /span>every class is also a type, to which all of its instances belong

<= ![if !supportLists]>n    The behavior of an object is provide by special functions, called methods

<= ![if !supportLists]>q   <= /span>methods are stored in variables associated with its class=

<= ![if !supportLists]>q   <= /span>methods are called with the syntax
<object expression>.<method name>(<parameter&g= t;…)

Object attributes

<= ![if !supportLists]>n&nb= sp;  The fields and methods of an object are known collectively as its attributes

<= ![if !supportLists]>n   Each instance (object) has its own set of fields

<= ![if !supportLists]>n   All instances of the same class share the same set of methods=

Messa= ge passing

<= ![if !supportLists]>n   The message passing metaphor is used to describe method calls=

<= ![if !supportLists]>q   <= /span>the value of the <object expression> is obje= ct to which the message is sent, called the target of the call

<= ![if !supportLists]>q   <= /span>the <method name> and the <parameter> values (arguments of the call) are the message

<= ![if !supportLists]>n   In every Python method call, the first formal parameter of the method, traditionally named self, is assigned the target of the call

<= ![if !supportLists]>q   <= /span>hence every Python method must have at least one formal parameter

<= ![if !supportLists]>q   <= /span>remaining formal parameters are bound to the call arguments as usual

<= ![if !supportLists]>q   <= /span>in Java, the keyword this has the same mean= ing as self

Class syntax

<= ![if !supportLists]>n    Class statement syntax (simplified)

class <class name> :

    def <method name> ( self [, <parameter>, ̷= 0;, <parameter> ] ) :

        <method body>

    +…

<= ![if !supportLists]>n    Example: a class with a single field, value, and associated getter and setter methods

>>> class Value:

        def setValue(self, value):

        =     self.value =3D value

        def getValue(self):

        =     return self.value

 

>>> v =3D Value() # create an instance of the Value class

>>> v.setValue(3)

>>> v.getValue()

3

Insta= nce creation

<= ![if !supportLists]>n    Instance creation expression syntax

<= ![if !supportLists]>q   <class name> (= <argument exp>, …, <argument exp> )

<= ![if !supportLists]>n    Semantics

<= ![if !supportLists]>q   <= /span>a new instance of the named class is created and returned=

<= ![if !supportLists]>q   <= /span>if the class has a constructor method named __init__, = it is called with the new instance (as self) and the argument expression values

<= ![if !supportLists]>q   <= /span>an error is raised if there are argument expressions and there is not method named __init__ accepting the given number of arguments

<= ![if !supportLists]>q   <= /span>the purpose of the constructor is usually just to create the new instance's fields

<= ![if !supportLists]>n    Example

<= ![if !supportLists]>q   <= /span>add to the Value class just defined

def __init__(self, value) :

    self.valu= e =3D value

<= ![if !supportLists]>q   <= /span>then an instance can be created with an initial value

v =3D Value(3)

OOP example: class RandomPick

<= ![if !supportLists]>n    Class RandomPick documentation

<= ![if !supportLists]>q   <= /span>RandomPick(items) returns an instance of the RandomPick class that allows random selection of elements from the items sequence

<= ![if !supportLists]>q   <= /span>.pick() returns an element pic= ked at random (without replacement) from the items remaining in the pick collec= tion

<= ![if !supportLists]>q   <= /span>.isEmpty() returns a boolean indicating if all items in the collection have been picked

<= ![if !supportLists]>n    Usage example

>>> rp =3D RandomPick('abc')

>>> rp.pick()

'c'

>>> rp.pick()

'a'

>>> rp.pick()

'b'

>>> rp.isEmpty()

True

Class= RandomPick implementation

import random

 

class RandomPick:

 

    def __init__(self, items):

        self.items =3D list(items) # copies lists

 

    def pick(= self):

        # randomly pick self.items index

        i =3D random.randrange(len(self.items))

        return self.items.pop(i)

 

    def isEmpty(self):

        return self.items =3D=3D []

=  

Tables (not in text)

<= ![if !supportLists]>n   Tables are the basis of spreadsheets, databases, and much graphical and other nume= ric computation, among other common uses

<= ![if !supportLists]>n   Tables are most naturally represented in Python as a list of lists (or perhaps usi= ng tuples instead of lists)

<= ![if !supportLists]>q   <= /span>rows are represented by sublists=

<= ![if !supportLists]>q   <= /span>columns are obtained by selecting the elements wit= h a given index from all the rows

<= ![if !supportLists]>q   <= /span>all rows are assumed to be of the same length=

<= ![if !supportLists]>n&nb= sp;  We abstract table operations by defining cla= ss Table

and documentation convention (not in text)

<= ![if !supportLists]>n   Assert statement

<= ![if !supportLists]>q   <= /span>syntax: assert <expression>

<= ![if !supportLists]>q   <= /span>semantics: throws an exception if <test expression> is not true

<= ![if !supportLists]>q   <= /span>valuable for

<= ![if !supportLists]>n   &nb= sp; automatic testing

<= ![if !supportLists]>n   &nb= sp; catching errors running programs

<= ![if !supportLists]>n   &nb= sp; making programs easier to understand

<= ![if !supportLists]>q   <= /span>example: assert 1 + 2 =3D=3D 3

<= ![if !supportLists]>n   Documentation convention: from now on, assume all mutation methods and functions return <= b>None unless noted otherwise

 =

Class= Table, documentation

<= ![if !supportLists]>n    Table(data<= /span>) constructs a new Table instance with rows being the elements of the given list of lists=

<= ![if !supportLists]>q   <= /span>assume each row is the same length

<= ![if !supportLists]>q   <= /span>data could be a sequence of sequences with associated immutability

<= ![if !supportLists]>n    .getData() returns the table data structure=

<= ![if !supportLists]>n    .ref(rowIn= dex, colum= nIndex) returns the value= of the element at the indicated row and column

<= ![if !supportLists]>n    .set(rowIn= dex, colum= nIndex, valu= e) assigns value<= b> to the element at the indicated row and column

<= ![if !supportLists]>q   <= /span>rows must be lists

<= ![if !supportLists]>n    .row(rowIn= dex) returns the indic= ated row

<= ![if !supportLists]>n    .column(colum= nIndex) returns a list wh= ose elements are those in the indicated column

Modul= e Table, test code

<= ![if !supportLists]>n We define a table module (in a file named table.py)

<= ![if !supportLists]>n It is a good idea= to write the test code first

def main():

    t =3D Table([['a', 1, True], ['b', 2, False]])

    assert t.getData() =3D=3D [['a', 1, True], ['b', 2, False]]<= /p>

    assert Fa= lse =3D=3D t.ref(1, 2)

    t.set(1, = 2, True)

    assert Tr= ue =3D=3D t.ref(1, 2)

    assert [1= , 2] =3D=3D t.column(1)

    assert ['= b' , 2, True] =3D=3D t.row(1)

   

if __name__ =3D=3D '__main__':

    main()

Class= Table, definition

class Table:

    def __init__(self, data):

        self.numCols =3D len(data[0])

        self.table =3D data

 

    def getData(= self):

        return self.table

 

    def ref(self, rowIndex, colIndex):<= o:p>

        return self.table[rowIndex][colIndex]

 

    def set(s= elf, rowIndex, colIndex, value):

        self.table[rowIndex][colIndex] =3D value

 

    def row(s= elf, rowIndex):

        return self.table[rowIndex]

 

    def column(self, columnIndex):

        col =3D []

        for row in self.table:

        =     col.append(row[columnIndex])

        return col

Addin= g zip to your code

<= ![if !supportLists]>n   The built-in function zip takes one or more sequences and returns a list= of tuples, with each tuple containing the values of sequence elements with the same index

<= ![if !supportLists]>q   <= /span>if some sequences are longer than others, values p= ast the length of the shortest sequence are ignored

<= ![if !supportLists]>q   <= /span>like a zipper that locks together teeth in the same position on each side

=  

>>> zip('abc', 'def')

[('a', 'd'), ('b', 'e'), ('c', 'f')]

>>> zip('abc', [1, 2, 3], (True, False, True))

[('a', 1, True), ('b', 2, False), ('c', 3, True)]<= /b>

Table exercise 1

<= ![if !supportLists]>n   Add the following methods

<= ![if !supportLists]>q   <= /span>.dimensions() retur= ns number of table rows and columns

<= ![if !supportLists]>q   <= /span>.addRow(row) adds row to end of table

<= ![if !supportLists]>n   &nb= sp; assume row length equals the number of columns in the current table

<= ![if !supportLists]>q   <= /span>.addColumn(colum= n) adds column= to right side of table

<= ![if !supportLists]>n   &nb= sp; assume column length equals number of rows in the current tab= le

<= ![if !supportLists]>n     hint: = use a for loop iterating over a list created by zip

<= ![if !supportLists]>n   Additional tests for these methods

assert t.getData() =3D=3D [['a', 1, True], ['b', 2, True]]

t.addRow(['c', 3, False])

t.addColumn(['the', 'last', 'column'])

assert t.getData() =3D=3D [['a', 1, True, 'the'],<= /b>

        =             &nb= sp;  ['b', 2, True, 'last'],

        =             &nb= sp;  ['c', 3, False, 'column']]

Table exercise 1, solution

     def dimensions(self):

        return (len(self.table), self.numCols)

   

    def addRow(self, row):

        self.table.append(row)

 

    def addColumn(self, column):

        for row, value in zip(self.table, column):

        =     row.append(value)

        self.numCols +=3D 1

       

   

Table=   exercise 2

<= ![if !supportLists]>n   Add the following methods

<= ![if !supportLists]>q   <= /span>.deleteRow(rowIn= dex) deletes the indic= ated row from the table

<= ![if !supportLists]>q   <= /span>.deleteColumn() deletes the indic= ated column from the table

<= ![if !supportLists]>q   <= /span>.printTable(print= ColWidth) prints the table = with column width being the absolute value of printColWidth, left justifi= ed if it is negative, and otherwise right justified

<= ![if !supportLists]>n   Additional tests for these methods

assert t.getData() =3D=3D [['a', 1, True, 'the'],<= /b>

        =             &nb= sp;  ['b', 2, True, 'last'],

        =             &nb= sp;  ['c', 3, False, 'column']]

t.deleteRow(0)

t.deleteColumn(0)

assert t.getData() =3D=3D [[2, True, 'last'], =

        =             &nb= sp;  [3, False, 'column']]

Table exercise 2 solution

 

   <= span style=3D'font-size:7.0pt;font-family:"Courier New";mso-bidi-font-family:"Co= urier New"; mso-bidi-language:#AC45'>def deleteRow(self, rowIndex):

        del self.table[r= owIndex]

 

    def deleteColumn(self, columnIndex):

        self.numCols -=3D 1

        for row in self.table:

        =     del row[columnIndex]

 

    def printTable(self, columnWidth):

        for row in self.table:

        =     line =3D ''

        =     for value in row:

        =         if columnWidth < 0:

        =             s =3D str(value).ljust(-columnWidth)

        =         else:

        =             s =3D str(value).rjust(columnWidth)

        =         line +=3D s

        =     print line