CSC207 Software Design
Lectures
Object-Oriented Python

Review

An object is an instance of a class

Class defines possible operations and states

Each instance is independent of all others

Object-oriented language supports:

Encapsulation: each instance manages all of its own state

Polymorphism: objects with the same operations are interchangeable

Inheritance: define new classes by extending existing ones

Reflection: programs can inspect themselves at run-time

Not part of the official definition

But very important in practice

Creating a Class in Python

A source file may define any number of classes

Start definition using class keyword

Followed by name of class

Starts an indentation block

Functions defined in class block are methods

Must have at least one argument

Represents the particular instance

Usually called self

Methods can be called anything

But method names beginning and ending with double underscore mean special things

E.g. __init__ is the class's constructor

Members created by assignment to self.whatever

A Simple Counter Class

class Counter:

    # Constructor: 'self' is the object being constructed
    def __init__(self):
        self.value = 0          # Create a member variable 'value'

    # Normal method: 'self' is the method's object
    def step(self):
        self.value += 1

    def current(self):
        return self.value

if __name__ == "__main__":
    c = Counter()
    print "initial value", c.current()
    c.step()
    print "after one step", c.current()
    c.nonExistentMethod()

initial value 0
after one step 1
AttributeError: Counter instance has no attribute 'nonExistentMethod'

Encapsulation Revisited

Python doesn't enforce encapsulation

No equivalent of protected or private

Anyone can happily execute obj.value = "abc"

Generally a bad idea

New Classes From Old

Extend a parent class to create a child class

Put parent's name in parentheses after child's

Must invoke parent's constructor explicitly

Unlike Java, it can be called like any other method

from counter import Counter

class Stepper(Counter):

    def __init__(self):
        Counter.__init__(self)

    def reset(self):
        self.value = 0

Overriding Methods

Methods defined in child take precedence over those defined in parent

from counter import Counter

class Incrementer(Counter):

    def __init__(self, increment=1):
        Counter.__init__(self)
        self.increment = increment

    def step(self):
        self.value += self.increment

if __name__ == "__main__":
    objects = [["parent", Counter()],
               ["child",  Incrementer(3)]]
    for i in range(2):
        for [name, obj] in objects:
            obj.step()
            print name, i, ":", obj.current()

parent 0 : 1
child 0 : 3
parent 1 : 2
child 1 : 6  

Class Members

Variables defined directly in class belong to the class, not to instances

Like static in Java

Nothing equivalent (yet) for methods

Concept is easy

Coming up with a simple syntax has proved difficult

class Tracker:
    numCreated = 0
    def __init__(self):
        Tracker.numCreated += 1

t1 = Tracker()
t2 = Tracker()
print Tracker.numCreated

2

Overloading Operators

Specially-named method associated with every arithmetic operator

__add__ for +, __mul__ for *, and so on

If x is an object, x+2 is really x.__add__(2)

Operators also have right-hand methods

E.g. __radd__, __rmul__

So 2+x is x.__radd__(2)

Execution order for a+b is:

If a has a method __add__, call a.__add__(b)

Else if b has a method __radd__, call b.__radd__(a)

Else use Python's built-in default

Modular Integer Class

Only takes on values 0..N-1

Automatically wraps around at N

class modInt:

    def __init__(self, base):
        self.base = base
        self.value = 0

    def __add__(self, other):
        self.value += other
        self.value %= self.base
        return self

    def val(self):
        return self.value

if __name__ == "__main__":
    a = modInt(3)
    for i in range(5):
        a = a + 1
        print a.val()

1
2
0
1
2

Some Other Special Methods

__str__(self) Convert to string (e.g. for printing)
__call__(self, args) Function call
__getitem__(self, index) Indexing
__contains__(self, item) Membership test
__len__(self) Length
__int__(self) Convert to integer

Sparse Vector Class

Problem: want efficient storage when almost all elements are zero

Solution: store index/value pairs of non-zero elements in a dictionary

What is the length of a sparse vector?

Some maximum value specified by the user?

Largest current index?

Largest index seen so far?

We'll use largest index seen so far

Methods

Length

Element access (get and set)

Elementwise addition

Printability (string conversion)

Skeleton

Note: unassigned string at start of class, method, or function is documentation

Access as class.__doc__ or func.__doc__

class SparseVec:
    """Implement sparse vector using index-to-value dictionary."""

    __init__
    __len__
    __getitem__
    __setitem__
    __add__
    __str__

Initialization and Length

    def __init__(self, vals={}):
        """Construct new vector."""
        self.val = {}
        self.val.update(vals)
        self.len = max([0] + vals.keys())

    def __len__(self):
        "Notional length of vector."""
        return self.len

Getting and Setting Items

    def __getitem__(self, i):
        """Get value at index, or 0."""
        return self.val.get(i, 0.0)

    def __setitem__(self, i, n):
        """Set value at index."""
        self.val[i] = n
        self.len = max(i, self.len)

Adding Vectors

    def __add__(self, other):
        """Add two vectors."""
        result = SparseVec()
        for i in self.val:
            result[i] = self[i] + other[i]
        for j in other.val:
            if j not in self.val:
                result[j] = other[j]
        return result

Converting to String

    def __str__(self):
        """Convert vector to string."""
        s = "["
        if self.len > 0:
            for i in range(self.len+1):
                s += str(self[i])
                if i < self.len:
                    s += ", "
        s += "]"
        return s

Testing

if __name__ == "__main__":
    x = SparseVec({1:0.1, 3:0.3})
    y = SparseVec({1:1.0, 2:2.0, 4:4.0})
    print "x:", x
    print "len(x):", len(x)
    print "y:", y
    print "x+y:", x+y
    x[2] = 0.2
    print "x is now:", x
    print "y+x:", y+x
x: [0.0, 0.1, 0.0, 0.3]
len(x): 3
y: [0.0, 1.0, 2.0, 0.0, 4.0]
x+y: [0.0, 1.1, 2.0, 0.3, 4.0]
x is now: [0.0, 0.1, 0.2, 0.3]
y+x: [0.0, 1.1, 2.2, 0.3, 4.0]

$Id: pyobj.html,v 1.1.1.1 2004/01/04 05:02:31 reid Exp $