Date manipulation and Java 'interface' equivalents

Alex Martelli aleaxit at yahoo.com
Wed Nov 8 06:16:04 EST 2000


"Quinn Dunkan" <quinn at dinar.ugcs.caltech.edu> wrote in message
news:slrn90i5bd.rvo.quinn at dinar.ugcs.caltech.edu...
    [snip]
> interfaces or casts.  You almost never need to know or even care what
'kind'
> an object is... just call whatever method you want on it.  If it accepts
the
> method, then that's all you need to know about its type (if it doesn't you
get
> an exception).  I.e. if your Knobs and Dials have a 'twist' method, that's
all
> the client code cares about.  It couldn't care less what 'class' they
belong
> to.  I think that's what Alex meant by the "the method is the type" stuff.

Yep, basically.

To be fair about it -- there _are_ (small) advantages to grouping
sets of methods into interfaces.

For example, no "accidental naming conflicts".  E.g., a classical
example...:

Say that you're implementing objects that need to interface to
two frameworks -- frameworks that were developed independently
from each other.

One is a graphical fw, which handles your objects by (among other
things) sometimes calling a method on them, called "draw", that
tells them to re-draw themselves onscreen (your objects, to this
fw, represent "drawables").

One is a lottery-administration fw, which handles your objects
by (among other things) sometimes calling a method on them,
called "draw", that tells them to generate another random number
(your objects, to _this_ fw, represent "lotteries").

Ooops...!-)  Accidental naming conflict.

This is somewhat of a problem in most languages, by the way;
the grouping-into-interfaces does not necessarily solve it
"magically".  E.g., in Java, you can't usefully have your
object's class have both "implements Drawable" as well as
"implements Lottery" (as that would erase the namespace
distinction -- you'd have to define/override just ONE method
'draw', so the problem is on you again).  You solve this
(typically/idiomatically) with 'inner classes' -- but this
also means the _frameworks_ cannot be the one 'navigating'
(with casts) among your objects' interfaces... rather, things
have to be arranged so it's _you_ passing either the 'Drawable'
or the 'Lottery' ``view'' of your objects.

It's slightly better, in a sense, in C++, though you have
to "rename" one or both versions of 'draw' in an intermediate
utility class; once that is done, it's again possible for
the frameworks to be the ones 'navigating' (dynamic_cast)
among your objects' interfaces.

Visual Basic (sorry folks!-) is better in this, since when
you define a class that 'implements Drawable' _and_ also
'implements Lottery', then the methods you have to define
in the class are *automatically* to be named Drawable_draw
and Lottery_draw in their definitions.  Eiffel's powerful
renaming-while-inheriting mechanisms are also ok with this,
though not as simple and automatic as Visual Basic's.

In the COM native object model, navigation between interfaces
of an object is handled _very_ explicitly ("explicit is
better than implicit" is not ONLY a Python mantra:-), by
a dedicated 'QueryInterface' method that all objects _must_
implement.  While a 'cast' of any kind is handled implicitly
by the language and its runtime system, QueryInterface is
something that YOU implement, in YOUR code, and therefore
it's quite easy to handle things.  COM is the object model
that VB is (more automatically/implicitly/magickally) working
towards, by the way (VB, like most COM C++ frameworks, handles
tasks such as implementing QueryInterface on your behalf).


OK, so, what is Python to do in such a case?  A Python
object gets to "explicitly handle navigation", in that its
__getattr__ method gets called when client code asks for
an attribute (including a method) that is not in its
__dict__ (or inherited); in this way, it gives you even
more control than COM does.  *However*... the *granularity*
of this control is *a single attribute*.  There is no
concept of "interface", i.e. "named group of attributes";
thus, your __getattr__ just *doesn't get enough context
information* (no "interface-name"...) to be able to tell
whether it should supply Drawable_draw or Lottery_draw
when it's asked for a method named 'draw'!

So, despite the excellent 'explicit control', you're
not out of the woods.  You basically need two objects
(that share state), with different implementations of
method draw.  There _is_ no "navigation" (because there
are neither casts, nor QueryInterface, in the Python
object model) -- methods get called directly on the
object that the framework (or, the client-code) has in
hand; thus, to behave different on calls to draw from
different 'sides', different objects must be originally
given to the different sides.

Nothing dramatic, mind you -- even though you don't
get the use of Java's "inner classes", that use is
mostly in helping you bypass for such special cases
the barriers that Java normally erects; in Python, no
barriers, thus no special need to bypass them either.

But, enough verbiage, let me give an example -- a
Python solution to the accidental renaming problem
that just uses Python's normal 'metaprogramming'
approach, to reach much the same situation you would
have in Java with inner classes.  A toy example
of the Draw-vs-Lottery situation above, with a
data-member to show that we are indeed sharing state:

class MyClass:
    def __init__(self):
        self.calls = 0
    def Lottery_draw(self):
        self.calls += 1
        print 'lottery_draw',self.calls
    def Drawable_draw(self):
        self.calls += 1
        print 'drawable_draw',self.calls
    def asLottery(self):
        return Aux(self, {'draw': self.Lottery_draw} )
    def asDrawable(self):
        return Aux(self, {'draw': self.Drawable_draw} )

class Aux:
    def __init__(self, delegate, auxdict):
        self.__dict__=delegate.__dict__
        self.__dict__.update(auxdict)

Note that Aux is completely general: it creates a
"synonym" for the 'delegate' object (different
identity, but same state), except for the differences
specified in the auxdict.  The asLottery/asDrawable
methods need not be methods -- you can wrap an 'Aux',
with _whatever_ renaming you desire, around any
object you wish, in any external piece of code (it's
nicer to have them as method where feasible, but this
can also let you do "impedance matching" on other
external Python libraries without changing them!).
[In some cases you could do that by inheritance, but
there's no _strict_ need for inheritance here].

[In Java, by contrast, the methods returning inner-class
"views" of a class do need to be methods of that class,
but you can generally arrange that by inheritance... as
long as somebody hasn't inappropriately used a "final"
attribute in the class you're trying to wrap/reuse!]

Here's example usage...:

>>> me=MyClass()
>>> me.Lottery_draw()
lottery_draw 1
>>> me.Drawable_draw()
drawable_draw 2
>>> me.draw()
Traceback (innermost last):
  File "<pyshell#61>", line 1, in ?
    me.draw()
AttributeError: 'MyClass' instance has no attribute 'draw'
>>> dr=me.asDrawable()
>>> dr.draw()
drawable_draw 3
>>> lo=me.asLottery()
>>> lo.draw()
lottery_draw 4
>>>


So, you will pass to the drawing-framework me.asDrawable(),
to the lottery-framework me.asLottery() [rather than
just 'me' in either case] and live happily ever after.


Incidentally, Python metaprogramming is no end of fun,
but in most cases it had better _stay_ 'just fun' -- no
real point using it to provide funky syntax sugar for
something that could be done much more simply in "Python
native" ways (by analogy -- remember the budding C
programmers that, many years ago, were using C's preprocessor
for such momentous tasks as '#define { BEGIN' and
'#define } END' just so as to give C some of the syntax
sugar flavour of languages they preferred...?  Well, most
syntax-sugar-level uses of Python are similarly silly!-).

But I think that this one (basically emulating a typical
Java "inner class" solution, or C++/Eiffel "renaming")
*IS* a good use of the metaprogramming mechanisms (if
you often find yourself using two or more frameworks and
thus occasionally needing it, of course!-).


The slight-but-non-zero handiness of 'interfaces' here
comes up when an interface has several methods, not just
one, of course (albeit, it's hopefully unlikely that
_many_ of them will conflict between interfaces...!).

But we can enrich the 'class Aux' context so that its
__init__ will explicitly take a string as interface
name, a list of strings as methods to be renamed, and
build its dictionary from them:

class Renamer:
    def __init__(self, delegate, itfname, metnames):
        self.__dict__=delegate.__dict__
        for met in metnames:
            self.__dict__[met] = getattr(delegate,itfname+'_'+met)

and change the two relevant methods in MyClass to:

    def asLottery(self):
        return Renamer(self, 'Lottery', ('draw',))
    def asDrawable(self):
        return Renamer(self, 'Drawable', ('draw',))

The example code works just the same as before...


Of course, if "interface" became an official Python
concept (e.g. as in http://www.prescod.net/pytypes/, or
http://www.zope.org/Members/jim/PythonInterfaces/Summary)
there shall be benefits (reasonably small benefits for
very little cost, if a minimalist approach is adopted;
reasonably large ones for higher cost, if a more
ambitious one -- "you get what you pay for"...:-).

But, you can already get _today_ most of the uses
of Java's interfaces (static, compile-time checks
excepted!), out of what Python already offers.  That
Python may well give you even more in the future,
should not deter you from enjoying it today!-)


Alex






More information about the Python-list mailing list