[Tutor] newbie-class differs from function?

Magnus Lycka magnus@thinkware.se
Tue Mar 11 06:21:02 2003


lugoteehalt@yahoo.co.uk wrote:
>Have got very stuck and it might help if I
>understood the difference, in hyper-simple terms, between
>a class and a conventional, programmer defined function.
>They seem the same to me.

Remember the mail recently with the subject
"Trying to find a value in a function"?

The poster made his life hard since he tried to get
context out of a function. He would have been much
better off using a class... They are not the same thing
at all.

You can say that a computer program consists of two
things: Data and instructions. Object-oriented programming
has gained it's popularity because it makes it easier to
structure programs with related instructions and data bundled
together. The main tool to achieve this is the class.

A python function is what you might more generically
call a subroutine. It's a sequence of instructions, and
some local data needed to perform whatever it is intended
to do. It often takes input parameters, and often returns
some data, but it's really completely decoupled from the
parameters and non-local variables it uses.

The main data structures in the program, that are used in
several functions, are typically defined outside of the
functions.

Classes are used to bundle closely related data structures
and functions together. It's difficult to explain the advantage
of this in hyper-simple terms, since you don't need classes
in hyper-simple programs. They help when things get difficult
and complex.

This isn't a Python issue, you can for instance look at
http://java.sun.com/docs/books/tutorial/java/concepts/ for
some explanations.

Here is a little example: A counter class that can be used to count
things. A counter instance will be reset when it's created, it can
count if you call its incr() method, and reset if you call its
reset() method. It's also capable of showing its current state if
you convert it to a string (e.g. with print or str()).

You create a Counter instance by calling the Counter class. If you
create several instances, they will all share the same methods, but
they will have their own context.

 >>> class Counter:
...     def __init__(self):
...         self.reset()
...     def incr(self):
...         self.counter += 1
...     def reset(self):
...         self.counter = 0
...     def __str__(self):
...         return str(self.counter)
...
 >>> c1 = Counter()
 >>> c2 = Counter()
 >>> print c1, c2
0 0
 >>> c1.incr()
 >>> c1.incr()
 >>> print c1, c2
2 0
 >>> c2.incr()
 >>> print c1, c2
2 1
 >>> c2.incr()
 >>> c2.incr()
 >>> print c1, c2
2 3

There are a few magic things happening here.
"c1 = Counter()" means that a new instance of the Counter
class is created, and the variable c1 refers to that. If
there is an __init__ method, this will then be executed to
give the instance the desired initial state. The instance
object is passed in as the first parameter, typically called
self.

c1 = Counter()
c1.incr()

is really a shorter (and preferred) form for

c1 = Counter()
Counter.incr(c1)

Finally, "print c1" or for instance "str(c1)" will cause
the Python interpreter to call Counter.__str__(c1).

As you see, the class encapsulates several functions/methods and
an attribute. Most classes have several attributes that are closely
related. Often, a class represents a noun in the specification
for a program, such as Customer, Invoice or AirlineTicket. In the
case of an Invoice, it will have attributes such as buyer, seller,
items, sum, salesTax, issueDate, daysToPay etc. It might have
methods like .isPaid() returning True or False and .lastPayDate()
returning issueDate + daysToPay etc.

Do classes still seem to be just like functions?

An important aspect of classes is inheritence! You can create
so-called subclasses that are specialized versions of existing
classes. Let's create a Counter that ticks when it's incremented!
We do that by overriding the incr() method in our subclass.

 >>> class TickingCounter(Counter):
...     def incr(self):
...         print "Tick"
...         Counter.incr(self)
...

The above means: TickingCounter is a Counter, but the incr()
method is implemented like this instead. Note that
TickingCounter.incr() calls Counter.incr(), passing on the
parameter self, which is the actual instance object.

 >>> tc1 = TickingCounter()
 >>> tc1.incr()
Tick
 >>> tc1.incr()
Tick
 >>> tc1.incr()
Tick
 >>> print tc1
3

Hang on! We never tried the reset method!

 >>> print tc1
3
 >>> tc1.reset()
 >>> print tc1
0
 >>> tc1.incr()
Tick
 >>> print tc1
1

Hm... Sometimes I want the counter to be associated to a name. Let's
create a NamedCounter class.

 >>> class NamedCounter(Counter):
...     def __init__(self, name):
...         self.name = name
...         Counter.reset(self)
...     def __str__(self):
...         return self.name + ": " + str(self.counter)
...
 >>> rats = NamedCounter('Rats')
 >>> rats.incr()
 >>> rats.incr()
 >>> mice = NamedCounter('Mice')
 >>> rats.incr()
 >>> mice.incr()
 >>> print mice
  Mice: 1
 >>> print rats
Rats: 3

See?

Just beacause we *can* use inheritance, doesn't mean we always
should. In this case, it might be better to modify the counter
class so that it will take an optional name, and print it if
it exists, and otherwise skip it. That makes it easier to
create a Named Ticking Counter class for instance.

Actually, a class can inherit from several classes. This is called
multiple inheritence, but it's a misused feature, that often causes
problems. Some languages, such as Java, has refrained from
allowing it. The Java designers claim that it's on purpose, to
reduce the risk of error. (Who knows...)

Let's look at multiple inheritence. (It's not hyper simple
any more I guess...) You then give several base classes, and
if there is an ambiguity between them, the behavaiour of the
one listed first will be used.

We'll try to make a Named Ticking Counter by subclassing
NamedCounter and TickingCounter.

 >>> class NamedTickingCounter(TickingCounter, NamedCounter):
...     pass
...
 >>> x = NamedTickingCounter('Hello')
Traceback (most recent call last):
   File "<interactive input>", line 1, in ?
TypeError: __init__() takes exactly 1 argument (2 given)

Rats! This didn't work. Since TickingCounter is listed first,
its version of __init__ is used, and that it the __init__ in
Counter, which doesn't know about the name. Let's switch
order between the base classes.

 >>> class NamedTickingCounter(NamedCounter, TickingCounter):
...     pass
...
 >>> x = NamedTickingCounter('Hello')
 >>> x.incr()
 >>> x.incr()

Sigh... Now we obviously got a plain NamedCounter. The incr()
method in TickingCounter is'nt used, since an incr-method
(inherited from Counter) is available in NamedCounter, which
is listed forst... :(

Actually, you can get around this in Python version 2.2 and
later by using the new style classes. You make Counter into
a new style class by inheriting (directly or indirectly) from
object. Then a more elaborate method will be used for
determining what method to use!

 >>> class Counter(object):
...     def __init__(self):
...         self.reset()
...     def incr(self):
...         self.counter += 1
...     def reset(self):
...         self.counter = 0
...     def __str__(self):
...         return str(self.counter)

Ah! A new style Counter class. :) No big
change...

 >>> class NamedCounter(Counter):
...     def __init__(self, name):
...         self.name = name
...         Counter.reset(self)
...     def __str__(self):
...         return self.name + ": " + str(self.counter)
...
 >>> class TickingCounter(Counter):
...     def incr(self):
...         print "Tick"
...         Counter.incr(self)

I have to redefine these two since they will still refer to the
old Counter class. They still look exactly the same though.

 >>> class NamedTickingCounter(TickingCounter, NamedCounter):
...     pass
...
 >>> x = NamedTickingCounter('Hello')
 >>> x.incr()
Tick
 >>> print x
Hello: 1

It works now :) but my general advice is still to stay away
from multiple inheritance. What I just did is generally not
a good idea. I'm much more likely to shoot myself in the foot
in the further development of my Counter classes if I use
multiple inheritance, than if I manage to get away without
it. Especially if there are several base classes with common
ancestors like here.

On the other hand, multiple inheritance might be useful for
adding features such as logging, persistence etc from so-
called mixin classes.


-- 
Magnus Lycka, Thinkware AB
Alvans vag 99, SE-907 50 UMEA, SWEDEN
phone: int+46 70 582 80 65, fax: int+46 70 612 80 65
http://www.thinkware.se/  mailto:magnus@thinkware.se