[Tutor] classes and objects

Daniel Yoo dyoo@hkn.EECS.Berkeley.EDU
Mon, 26 Jun 2000 00:19:24 -0700 (PDT)


On Mon, 26 Jun 2000, geoffrey bays wrote:

> I'm new to programming, and plowing through Learning Python by Lutz
> and Ascher. Much of it is understandable with some effort, but the
> chapter on classes and object oriented programming is a forty-page
> tour-de-force that leaves me breathless and babbling! Alan Gault's
> Python tutorial fro beginners has some reasonably digestable examples.
> Any other sources for examples, exercises for Python OOP? I'm also
> looking for easy, clear guidance on CGI programming with Python.


To address your second question, there's a particularly nice tutorial on
Python and CGI at:

  http://www.devshed.com/Server_Side/Python/CGI/

and there are many other links on CGI programming at:

  http://www.python.org/topics/web/basic-cgi.html


For your first question about OOP, I haven't found a link to an OOP
tutorial with Python yet.  (Most likely, I'm not looking hard enough.)  
However, OOP isn't too hard.  OOP is a way of structuring programs that
emphasize the construction of agents.  The example that I remember, when I
learned OOP, was that of bank accounts.  I hope the original author
forgives me for paraphrasing the example.  I'll try to recount it here.  
Warning: this is slightly long.


Imagine that we want to be able to work with bank accounts.  
Specifically, we want to associate with a bank account a 'name' and a
'balance'.  With the techniques we know right now, we can do this with a
hashtable.

###
myaccount = {'name' : 'My Account', 'balance' : 100}
###

Our accounts will have this sort of regular structure.  It might be easier
to make a function that constructs these hash tables, just so that the
programmer doesn't have to type so much.

###
def makeAccount(name, balance):
	return {'name' : name, 'balance' : balance}
###

Of course, this is just for convenience's sake.

Now that we have an 'account', we'd like to do certain things with
it.  For example, we'd like to be able to deposit and withdraw amounts
from the account.  We _could_ just directly modify our hash, but we want
to make things more understandable, so that anyone who reads our code
knows exactly what we're doing and why.

###
def deposit(account, amount):
	account['balance'] = account['balance'] + amount

def withdraw(account, amount):
	account['balance'] = account['balance'] - amount
###

And, of course, we'd like a way to summarize what our account has:

###
def summary(account):
	return "'%s' contains %s" % (account['name'], account['balance'])
###


Fairly simple stuff.  Then, we'd be able to use it like this:

###
myaccount = makeAccount('My Wonderfully Woeful Account', 0)
deposit(myaccount, 500)
withdraw(myaccount, 300)
print summary(myaccount)
###


Let's notice what sort of stuff we're doing with these accounts.  Almost
every function that we've written so far appears to take in an account as
an argument, and manipulates it in some way.  The only exception,
makeAccount, could be amended to fit this pattern by passing in an empty
hash to be initialized as an 'account':

###
def initAccount(account, name, balance):
	account['name'] = name
	account['balance'] = balance
###

That takes care of that.  Then we could use this as follows:

###
myaccount = {}
initAccount(myaccount, 'My whimsical deposit box', 25)
###

If we wanted to expand the functionality of this, we might add a few more
functions to gradually refine what sort of operations we can do with these
accounts.  In this case, we could think of an account as not just a
hashtable anymore, but as a type of 'thing', which has defined functions
that you can do to it.  It's as if we were saying something like, "Ok,
account, withdraw money from yourself."  "Ok, account, now summarize
yourself."

It's this view that characterizes OOP programming!  In OOP, we try to
structure our programs so that our system is filled with these things,
these objects, that we can command at our bidding.  If you've done
anything with I/O, you might already be familiar with the 'file' object.  
Whenever we open a file, what Python's doing is returning an initialized
file object.

###
myfile = open("temp.txt")
###

and as a file object, it has specific functions that act on it.  In
languages that don't directly support OOP, it would look something like:

### Warning, not python ***
close(myfile)
###

instead of the familiar pythonisque:

###
myfile.close()
###


To continue with that account example, we want to be able to say things
like:

###
myaccount.deposit(10)
myaccount.withdraw(42)
###

and we want to be able to see exactly what sort of operations accounts can
support with:

###
dir(myaccount)
###

In languages that don't directly support OOP, this is a bit hard, because
there's really no tight bonding that connect that hashtable with the set
of functions that work with it.  Who knows --- what if 'deposit()' was
meant to be used in a mail-dropping program, and not a financial program?  
Yes, we can write the two in the same file, true, and this has worked in
non-OOP languages pretty well.  However, OOP languages make this binding
more explicit, and this seems to aid people in the design of OOP programs.

So how does the notion of an account look like in OOP'ish python?  Here
goes:

###
class Account:
	def __init__(self, name, balance):
		self.name = name
		self.balance = balance

	def withdraw(self, amount):
		self.balance = self.balance - amount

	def deposit(self, amount):
		self.balance = self.balance + amount

	def summary(self):
		return "'%s contains %s" % (self.name, self.balance)
###

As you can see, it's pretty much the same stuff as above.  It is,
admittedly, shorter, but that's just a matter of syntax.  What's pretty
nice about this is that it enforces a few constraints on how withdraw(),
deposit(), and summary() are used --- there is no ambiguity that these
functions are meant to be used with accounts now, since they're wrapped
inside an "Account".

How do we use this, then?  Here's an example:

###
x = Account('My classy account', 1000)
print x.summary()
x.withdraw(1000)
print x.summary()
###

Plus, we have the added benefit of allowing people to look at the
functions associated with the class with dir():

###
>>> dir(Account)
['__doc__', '__init__', '__module__', 'deposit', 'summary', 'withdraw']
###

Still, this doesn't disguise the fact that, beneath this all, that
hashtable is in there somewhere:

###
>>> dir(myaccount)
['balance', 'name']
###

I hope this gives you a taste of what OOP is about.  I know I rushed
things a bit though, so if you have any questions on this, feel free to
ask again for clarification.  I have not even touched other aspects of
OOP, such as inheritence or polymorphism, that are possible because of
this restructuring of data and functions.  However, this conception of
programming as designing classes is at the heart of OOP.