a class's coupling to its bases (was Re: __init__ concerns)

Alex Martelli aleax at aleax.it
Tue Dec 11 05:33:32 EST 2001


"Erik Max Francis" <max at alcyone.com> wrote in message
news:3C158FAA.DA4A4BF4 at alcyone.com...
> Peter Wang wrote:
>
> > what is the proper python
> > idiom (or, failing that, the proper technique) for ensuring that a
> > subclass instance is properly initialized, especially when details of
> > the base class are unknown?
>
> I don't understand the circumstances under which the arguments to a base
> class constructor would be unknown.  You're not picking base classes at
> random, a class will require a certain base class and will be dependent
> on it.  Therefore, it will know the necessary arguments to pass to its
> constructor.
>
> Under what circumstances would you truly not know?

Case [1]: when using a framework by subclassing framework-supplied classes

You would not WANT to know, if at all feasible, because it loosens the
coupling between your code and some application framework that you're
using, when you have to inherit from some class in the application
framework.  Coupling should be held as loose as feasible (but no
looser than that...).

Classes are normally quite tightly coupled to their bases -- one of
the main downsides of inheritance.  Loosening that coupling in as far
as that's feasible is definitely NOT a bad idea.  Particularly when
the framework is subject to future releases (if you're using a "dead"
framework, whose interfaces, at least, will never change forevermore,
then you're less vulnerable to this issue).

A framework's classes often provide "template-method" design patterns
and other design characteristics that mean the main way to use the
framework is to inherit your classes from those supplied by the
framework for this purpose.  This is a common use of inheritance.

Of course, when you perform such inheritance you become coupled, to
some extent, to the design details of the classes you're inheriting
from.  Specifically, there will be some methods you need to override,
or provide anew (cfr the Command and sgmllib modules in the Python
standard library for a few examples).

However, the framework classes you're inheriting from may have many
other methods besides those you NEED to be concerned with.  If you
can avoid being coupled to the details of those OTHER methods, then
your classes will be less tightly coupled to the framework's.  One
of the methods for which it's nice to avoid tight coupling, if and
when feasible, is indeed __init__.

See:
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52235
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52236
for a couple of indications on how to proceed for this purpose.

We've done mucho editing of these (and other:-) recipes in preparation
for the paper-version of the Cookbook, though a lot remains to be done
yet; the current versions are substantially enhanced.  Once the editing
tasks are done, the plan is to update the online ASPN version of the
Cookbook with the latest-and-greatest versions -- in many cases, for
example, the enhancements have to do with considering non-classic classes
of Python 2.2 and what benefits or problems they bring to several of
the recipes.  Unfortunately, the editing is so time-consuming right now
that I can't yet find the time to update the online versions (indeed, I
have even had to suspend my participation in comp.lang.python, except
for occasional bouts like right now when I find myself at a location
where I can't be working on the Cookbook...:-).


Case [2]: generic decoration

A highly Pythonic pattern (perhaps not used as much as it could/should
be, because not highly publicized) is to inherit from a class that is
not known at code-writing time.  For example:

def FubbleDecorator(baseklass, cheerp, zlung):
    class decoratedklass(baseklass):
        # wonders and marvels based on cheerp and zlung
        # e.g.:
        def __init__(self, *args, **kwds):
            baseklass.__init__(self, *args, **kwds)
            self._cheerp = cheerp
            self._zlung = zlung
        def __getattr__(self, name):
            self._cheerp.write(self._zlung%name)
            return baseklass.__getattr__(self, name)
    return decoratedklass

etc, etc.  There are examples of such idioms on the Cookbook, too.

Clearly, such functions, manipulators of generic classes that may
well want to inherit from the classes they're decorating (classes
which are unknown at code-writing time, e.g. as they'll be passed
as arguments later on) are made most useful and general by having
the loosest possible coupling to any characteristics of the classes
being decorated.  And this can take different forms, too, e.g.,
even at module level:

class MyApplicationClass(base1, WhateverNeeded(), SomethingElse()):
    # whatever extras we need, if any
    pass

again, base classes are unknown, or at least not rigidly determined,
at code-writing time; the looser the coupling you can manage, the
more general and usable your code.


Generic programming, e.g. as supported by C++ templates, displays
some of these cases, too.  In C++'s cases, all classes are known
at compile-time, BUT code-writing time is earlier, and indeed most
templates are best written with as loose a degree of coupling as
possible to their parameters, including those type parameters that
are to be used as base-classes within the template.

Indeed, behind the veil of utterly different syntax, the amount of
similarity between Python programming and generic programming (in
particular, C++ template programming) is sometimes astounding.  The
lessons learned by writing C++ templates can sometimes be applied
to programming Python, and vice versa, despite the many obvious
semantic differences (compile-time vs run-time instantiation, for
example, but also the many differences in scoping, overloading,
type-checking, and so on).


Alex






More information about the Python-list mailing list