[Tutor] OOP programming principles overview? [Complex numbers and OOP]

Danny Yoo dyoo@hkn.eecs.berkeley.edu
Tue, 20 Nov 2001 19:29:02 -0800 (PST)


It might help if we browse an extended example that has some of the spirit
of OOP, without jumping directly into OOP syntax.  If we do this, the
concepts might become more clear.


Let's say that we're trying to make a program that helps us compute with
complex numbers.  (Complex numbers are already built into Python, but
they're a great example of the "object" concept --- I can't resist using
them... *grin*)  If complex numbers give you nightmares though, we can
write a different example if you want.

[Warning: very very long example ahead.  I need to read Strunk and White.]


In order to work with complex numbers, we'll need to decide how to
represent them.  Let's handle that first.

###
def makeComplex(real, imag):
    """Creates a structure that represents a complex number."""
    return (real, imag)
###

We've arbitrarily chosen to use a tuple to represent a complex number ---
we could have used a dictionary or list instead... any plain container for
these values would have worked.


Also, it might be nice to write small functions to get at the real() and
imag()inary parts of a complex number.  This is friendly because we allow
users to not have to remember if the first part of a complex number is the
real or imaginary part.

###
def real(c): return c[0]

def imag(c): return c[1]
###


Let's test what we have right now:

###
>>> m = makeComplex(42, -42)
>>> real(m)
42
>>> imag(m)
-42
###


Simple enough.  What sort of things can we use with complex numbers?  We
use numbers in calculation, so it might be good to be able to add() and
multiply() complex numbers together.  Complex numbers have a fairly simple
rule for addition:

###
def add(c1, c2):
    """Return a new complex number that represents the addition of
    two complex numbers c1 and c2"""
    return makeComplex(real(c1) + real(c2),
                       complex(c1) + complex(c2))
###

but the multiply() rule for complex numbers looks quite obscure.  *grin*

###
def multiply(c1, c2):
    """Return the product of two complex numbers c1, c2,
    according to the math rule:
        (A + Bi) * (C + Di) = (A*C - B*D, A*D + B*C)."""
    a, b = real(c1), imag(c1)
    c, d = real(c2), imag(c2)
    return makeComplex(a*c - b*d,
                       a*d + b*c)
###



Let's test out multiply(), since I feel a little nervous about it:

###
>>> forty_five_degrees = makeComplex(0.707, 0.707)
>>> b = makeComplex(1, 0)
>>> for i in range(8):
...     print b
...     b = multiply(b, forty_five_degrees)
... 
(1, 0)
(0.70699999999999996, 0.70699999999999996)
(0.0, 0.99969799999999986)
(-0.70678648599999983, 0.70678648599999983)
(-0.99939609120399975, 0.0)
(-0.70657303648122782, -0.70657303648122782)
(0.0, -0.99909427358445613)
(0.70635965142421042, -0.70635965142421042)
###

Whew.  Ok, that actually worked.  Cool.  As a side note, complex numbers
are pretty neat because we can use them to rotate points in 2d space... if
we intentionally confuse the idea of "complex number" and "coordinate".  
In the example above, I multiplied 'b' repeatedly by 'forty_five_degrees'.  
We can see that 'b' is circling around like a bee.



If this is mostly understandable, then you don't have to worry too much,
because we've just gone through many of the core OOP concepts.  *grin*

OOP is a way of writing programs so that we concentrate on data and the
sort of things this data "knows" how to do.  In some sense, thinking in
OOP is like personifying our programs.  In the example above, we'd say
that all of the definitions that we've written:

###
def makeComplex(real, imag):
    """Creates a structure that represents a complex number."""
    return (real, imag)

def real(c): return c[0]

def imag(c): return c[1]

def add(c1, c2):
    """Return a new complex number that represents the addition of
    two complex numbers c1 and c2"""
    return makeComplex(real(c1) + real(c2),
                       complex(c1) + complex(c2))

def multiply(c1, c2):
    """Return the product of two complex numbers c1, c2,
    according to the math rule:
        (A + Bi) * (C + Di) = (A*C - B*D, A*D + B*C)."""
    a, b = real(c1), imag(c1)
    c, d = real(c2), imag(c2)
    return makeComplex(a*c - b*d,
                       a*d + b*c)
###

are the raw materials for a "class".  In obscure OOP lingo, we've defined
a "constructor" that knows how to make "instances" of a complex number.  
In this light, makeComplex() is a "constructor" of complex numbers.

We've also defined a few functions that work intimately with complex
numbers.  In OOP lingo, these are called "members" of a class.  Dunno why
OOP gives these concepts such wacky names, but that's how it goes...
*grin*


What Python provides is a formal syntax specifically for writing classes.  
Let's take a look and see what a ComplexNumber class might look like if we
use this syntax.

###
class ComplexNumber:
    def __init__(self, real, imag):
        self.data = (real, imag)

    def real(self): return self.data[0]

    def imag(self): return self.data[1]

    def add(self, other):
        return ComplexNumber(self.real() + other.real(),
                             self.imag() + other.imag())

    def multiply(self, other):
        a, b = self.real(), self.imag()
        c, d = other.real(), other.imag()
        return ComplexNumber(a*c - b*d,
                             a*d + b*c)
###

You don't have to understand the syntax yet.  What's important to see is
that the definition here is really quite close to the code we had above.  
If it helps, flip back and forth between the two, to compare the
similarities between the two definitions.  OOP is not a crazy concept ---
it just dresses up very badly sometimes.  *grin*



The big jump to from procedural programming syntax to Object Oriented
syntax is analogous to the jump in English from passive voice to active
voice: instead of emphasizing functions and its arguments:

    In a "procedural" mode of thinking:
        real(c) -->
            Translation: "real() is called on 'c'."

        multiply(c, forty_five_degrees) --> 
            Translation: "multiply() is being called on 'c' and
                          'forty_five_degrees'."


Object oriented languages tend to rearrange the syntax to favor the data:

    In a "OOP" mode of thinking:
        c.real() -->
            Translation: "c calls real() on itself"

        c.multiply(forty_five_degrees) -->
            Translation: "c multiply()ies itself by forty_five_degrees"


I'd better stop at this point; this message is far too long already.  But
hopefully this helps a little bit.  Read Alan's book.  *grin*


Best of wishes!