[Tutor] Classes, Classes, Classes

Skip Montanaro skip@mojam.com (Skip Montanaro)
Fri, 12 Nov 1999 09:50:34 -0600 (CST)


    RP> Hello, I am experiencing trouble understanding classes. I don't
    RP> understand how they work, what they do and their purpose in
    RP> general. 

Classes are used to group data together with the functions (methods) that
operate on them.  The usual intention is that an object holds a number of
pieces of data and a functional interface (API) to those data are presented
to the world.  Since the data can change as requirements change (say, as new
algorithms are needed to improve performance), hiding the data
representation behind an API reduces the coupling between the class and the
code that uses it.

Perhaps the simplest example of this is the use of polar coordinates
vs. cartesian coordinates to represent points in 2D or 3D space.  If I
develop a class like:

    class Point:
        def __init__(self):
	    self.x = self.y = 0.0

	def set_cartesian(self, x, y):
	    self.x, self.y = x, y

	def set_polar (self, theta, radius):
	    self.x, self.y = mumble(theta, radius)

	def get_cartesian(self):
	    return self.x, self.y

	def get_polar(self):
	    return mumble(self.x, self.y)

the user need not know how I chose to represent 2D points, because there is
a method available to set and get both cartesian and polar representations.
If I decide for whatever reason that it's more efficient to represent points
in polar form, I can change the underlying representation of a point without
changing the API at all:

    class Point:
        def __init__(self):
	    self.theta = self.radius = 0.0

	def set_cartesian(self, x, y):
	    self.theta, self.radius = mumble(x, y)

	def set_polar (self, theta, radius):
	    self.theta, self.radius = theta, radius

	def get_cartesian(self):
	    return mumble(self.theta, self.radius)

	def get_polar(self):
	    return self.theta, self.radius

If I had exposed the internal representation as part of the API, by perhaps
presenting an incomplete set of methods:

    class Point:
        def __init__(self):
	    self.x = self.y = 0.0

	def set_polar (self, theta, radius):
	    self.x, self.y = mumble(theta, radius)

	def get_polar(self):
	    return mumble(self.x, self.y)

I would have forced the caller to access x and y directly to get and set the
cartesian coordinates.  I'd then be stuck if I decided it was best to
represent points internally as polar coordinates.

Python doesn't force this data hiding on you.  A caller can still access
point.x or point.y directly, but if you make your desires known and they
still sidestep your interface, that's their problem.  (As Guido says, "after
all, we are all adults.")

There is a subtle shift in perspective from an action-centered view of
things to a data-centered view of things that takes place somewhere along
the way.  When that happens I think most people have an "aha!" sort of
experience.  It suddenly gels and many problems seem to decompose naturally
into objects.  Until then (and perhaps frequently after) the world still
looks like a bunch of functions with data getting passed around as an
afterthought.

I've completely ignored issues of refinement (subclassing).  I'll let
someone else answer that part.

Skip Montanaro | http://www.mojam.com/
skip@mojam.com | http://www.musi-cal.com/
847-971-7098   | Python: Programming the way Guido indented...