detecting param type

Alex Martelli aleaxit at yahoo.com
Thu Oct 26 04:29:03 EDT 2000


"Jason Cunliffe" <jasonic at nomadicsltd.com> wrote in message
news:svf5ve5uegfdb1 at corp.supernews.com...
> Alex
>
> Thanks very much...

You're welcome!


> As I develop my methods for this module, I find I am adding more and more
> keyword arguments with defaults and more detection clauses to act upon
them,
> such as:
>
> def mymethod(index=0, display=0, sort='auto', event='something'): etc..
>
> I like this because as I am experimenting in PythonWin interactive shell
> where I can see my arguments appear inline and I find the code is easier
to
> maintain and more explicit. As things grow it seems like I should factor
out
> the common ones and instead put them into a superclass and thus make
faster
> changes later. For example if all my class methods have the same  set of 4
> or 5 key arguments, should'nt these go outside in one place where they can
> be improved.

A very acute observation.  Keyword arguments are very Pythonic, but
it IS possible to overdo them!  C++ decided to avoid keyword arguments
(as explained in Stroustrup's excellent "C++ Design and Evolution" book)
in good part because of that -- kw args are easy to overdo, and objects
offer a good alternative.

A superclass (of the class defining mymethod) is not necessarily the
best refactoring in this case.  Rather, consider an auxiliary-class,
call it an "argument-class", whose key purpose in life is grouping
the passing and handling of certain semantically related sets of
arguments, with appropriate defaults and keywords.

For example, suppose that you have a set of "how-to-display" specs,
that are only relevant if "display" is 'true': color, background,
height, width, aspect-ratio, font-face (family, weight, slant...)...

The specs are not necessarily independent -- for example, one can
specify at most 2 out of 3 for height/width/aspect-ratio (since
the latter is the ratio of the first two).  Further, within a given
client-program to your module, only a limited variety of settings
is likely to be needed for many of these, and of course you do NOT
want to specify, analyze, process them each and every time.

So, try something like...:

class Display:
    def __init__(self, copyfrom=None, foreground=None, background=None,
        height=None, width=None, aspect=None,
        fontface='courier', fontweight='normal', fontslant='roman'):
        if copyFrom:
            self.__dict__ = copy.copy(copyFrom.__dict__)
        if foreground!=None or background!=None or not copyFrom:
            self.Color(foreground, background)
        if height!=None or width!=None or aspect!=None or not copyFrom:
            self.Geometry(height, width, aspect)
        self.Font(fontface, fontweight, fontslant, copyFrom)
    def Background(self,background):
        if background is None: raise ValueError
        return self.Color(self.foreground,background)
    def Foreground(self,foreground):
        if foreground is None: raise ValueError
        return self.Color(foreground,self.background)
    def Color(self, foreground=None, background=None):
        if (foreground,background)==(None,None):
            foreground,background=self._DefaultColors()
        elif foreground is None:
            foreground=self._BestContrast(background)
        if background is None:
            background=self._BestContrast(foreground)
        self.foreground = foreground
        self.background = background
        return self
    def Geometry(self, height=None, width=None, aspect=None):
        # etc, etc -- rest snipped


Your functions/methods/classes needing to receive, record
or use display-related parameters can now do so via the Display
class:

    def myfun(index=0, sort='auto', display=None):
        # do the computations, then
        if display:
            window=screen.makeWindow(display.height,display.width)
            window.setColors(display.background,display.foreground)
            # etc, etc

and client code has an easy life calling you, too:

    myfun(display=Display())

to use entirely-default display parameters;

    myfun(display=Display(foreground='red'))
or
    myfun(display=Display().Foreground('red'))
to use defaults except for a red foreground-color (and the
default background algorithmically deemed as best-contrast
for that foreground);

    mydisplay=Display(aspect=2.5, foreground=black, fontface='Verdana')
    myfun(0, display=mydisplay)
    myfun(3, sort='down', display=mydisplay)
    myfun(2, display=mydisplay)

for a series of calls with the same display-settings;

    myfun(0, display=mydisplay)
    myfun(3, display=Display(mydisplay, aspect=3.0))
    myfun(2, display=mydisplay)

for a similar series but with a slight variation in one of the
display-settings with respect to the others;

etc, etc...


Now, this is a rather extreme, full-blown case.  "Do the simplest
thing you can possibly get away with" is still the golden rule,
remember: it's easy to get carried away with enthusiasm at the
vistas that a new pattern/idiom/technique/approach opens, and
end up with 'overblown' architectures with respect to your actual
needs.  Such a 'pharaonic' approach to auxiliary-argument-classes
would only be warranted in a library/module/package intended for
wide distribution, where you DO need high generality and regularity.

For *your own* needs, start simple, build up gradually; don't try
to design everything with the utmost possible generality right
from the start; Python is highly "plasmable" -- if you start simple,
it will be EASY to refactor-in a progressively more advanced and
rich architecture IF AND WHEN needed (easier if refactoring browsers
mature, but easy anyway even with text-editors:-).  The right time
to add a certain nugget of architectural-richness is when you KNOW
you will gain from its presence more than building and maintaining
it will cost you; when in doubt, keep it simple.  And if needs for
complexity, that once appeared certain, later disappear, don't
hesitate to refactor *towards simplicity*, even if this appears
to reduce architectural generality... generality [and complexity]
that you don't NEED is a COST, not a BENEFIT!  (Keep a RCS log of
every source and/or other "text-file subject to change/evolution"
you ever work on -- _always_ -- so you can always recover your
previous work if need be; don't clutter your sources by "commenting
out" stuff you don't need now but are unsure about for the future).

In other words, remember the Python mantras -- simple is better
than complex, complex is better than complicated:-).


Alex






More information about the Python-list mailing list