classes (was Re: Same again please for OOP)

Alex Martelli aleaxit at yahoo.com
Mon Dec 25 03:20:51 EST 2000


"Erno Kuusela" <erno-news at erno.iki.fi> wrote in message
news:kubsu1t81x.fsf at lasipalatsi.fi...
> >>>>> "Alex" == Alex Martelli <aleaxit at yahoo.com> writes:
>
> | "Roy Smith" <roy at panix.com> wrote in message
> | news:roy-E5B925.13324224122000 at news.panix.com...
> [...]
> || The mp3 class might return a URL for its location() method, and the
> || CD or Vinyl classes might return a string, "Third shelf from the
> || bottom,
>
> | Looks like a typical case of state mismodeled as behavior.  Why
> | should 'location' be a method -- what does it *DO*?!  It doesnt _do_
> | anything; rather, it's an 'accessor' for a part of the object's
> | state.
>
> i don't understand what is wrong with this - i like making them all
> methods, so i don't have to remember if it's an attribute or method
> later. also overrinding __getattr__ later, if it turns out to be
> needed, is tedious and ugly.

"Tedious and ugly"?!  Just add inheritance from the usual mixin:

class AccessorsMixin:
    def __getattr__(self, name):
        if name.startswith('__'): raise AttributeError,name
        return getattr(self, '__get_'+name)()

and implement method __get_location(self) the same way as you'd
implement location(self) in your style.  What could be easier?  If
you already have a __getattr__, it's equally OK to add this couple
of lines to it, or delegate to this one (delegating has pluses).

Giving accessor methods uniform names is hardly a minus, and
starting them with something suggesting 'get' is particularly good.

It's also easy and fast to get caching of computed attributes this
way -- a slightly modified mixin can cache away the values of
attributes it's accessing, and fetch them from cache later (with,
of course, an 'invalidate-cache' internal method too, to be called
when attributes will have to be recomputed).  This is not always
appropriate, but it's quite handy at times.


> why does the user want/need to know if "location" is stored directly
> as an attribute or if computation is needed to to reconstruct it
> from some other information?

He/she neither wants or needs to -- and what does this have to
do with the price of tea in China?  Access to location is obtained
from client-code by 'myobject.location' anyway: in the common
case where this state is stored (or cached) in an attribute, it's
accessed directly (remember __getattr__ isn't called for those
attributes which are in the object's __dict__!) -- in the rarer case
where accessor-computations are needed, they're transparently
performed.

As for 'what is wrong' with hiding state access behind methods
(most of which don't actually DO anything but fetch state), a
first example is:
    obj.price *= 1.07    # 7% increase
versus
    obj.setPrice(obj.getPrice() * 1.07)    # 7% increase

Why force client code to go through the latter, more complicated
form, when the simpler first form is available so cheap?

Further: Python classes are normally 'expando' (a term that
comes, I believe, originally from Javascript, but the concept
is of course wider) -- attributes can be added unless one takes
pains to disallow this.  Why force client-code to know and
remember which attributes are actually 'in' the original object
(and thus must be accessed through accessor methods, in
your style) and which ones aren't (and thus must be accessed
directly, unless one goes to the serious trouble of building up
an accessor method and sticking it on on-the-fly -- blah!)...?
Uniform attribute-access notation does away with the issue.


These are pragmatical considerations that make direct, simpler
notation preferable.  On a _conceptual_ plane, the key issue
is not confusing state and behavior.  "Methods BEHAVE -- they
DO stuff" and "properties JUST ARE -- I can 'read' them, and
some of them I may also be allowed to 'write'" is a good pair
of rules-of-thumbs to use as guidelines in deciding how to
model something -- method, or property (attribute)?  It's a
very similar distinction to that between functions and variables.

Would you like it if the language didn't let you write
    x = 23
and
    y = 6 + x
but forced you to code everything up as functions, e.g.
    set_x(23)    # instead of x = 23
or
    set_y(6 + x())    # instead of y = 6 + x
...?  I think this is part of why some of us prefer Python to
Scheme -- setq(y, +(6, x)) ("everything as functions") is
not all that natural to many of us.

So why should the author of client-code be any happier with
writing
    obj.set_y(6 + obj.x())
rather than
    obj.y = 6 + obj.x
...?  Other languages may well-nigh force one to frame
things up in terms of accessors/mutators, but that's an
issue with *those* languages -- not one we should copy
in Python, which lets us be more direct and simple.

I note in passing that 'property-notation' is a VERY popular
extension to all sorts of OO languages, *particularly* for
accessors even more than for mutators; e.g., while C++
does not allow for it in its standard, Borland first, and later
Microsoft, each added it as a non-standard extension to
their own versions of C++.  It may be framed as a silly little
piece of quite-dispensable syntax sugar (and I might agree),
but, in terms of mental separation between behavior and
state, it's conceptually commendable (although _in C++_
I would NOT use it in practice -- violating the standard in
this way has costs; but, in Python, it IS standard and easy).


I would not normally touch these subjects in a didactical
introduction to classes in Python -- they're far removed from
what should be a beginner's concerns regarding how to use
Python classes and instances most directly, simply, and
effectively.  In my view, the introduction should start with
'classes as simple "containers" of attributes [data, state]',
move on to initialization-behavior, then to other more
general cases of behavior, thence to polymorphism, and
on to inheritance, contain-and-delegate, and special
methods that allow instances to 'act like' numbers, sequences,
and so on.  __getattr__ and similar techniques would come
in pretty late in the curriculum -- at a point where the general
ideas of state and behavior are well-understood, and these
techniques fit in as very natural ways to maintain them.

But I guess it's good for the 'student' to be exposed early
on to the fact that controversy exists on these themes!-)


Alex






More information about the Python-list mailing list