Hi,

I wrote (on -users, but have moved the discussion here to -devel):
> > I was wondering, though, whether there'd be any support for some work
> > which tidied up the near-duplicate code in axes.py.

John Hunter replied:
> Certainly, but probably not using meta-classes.
> [...]
> I'm disinclined to use python black magic -- I find it makes the code
> harder to grok for the typical scientist, and these are our main
> developers.

Also, Darren Dale replied:
> It's true. That bit of code looks like Perl to me.

There's no need to be rude :-)  The generalised docstrings are a bit
ugly because they're unavoidably filled with phrases like "set the
x-axis limits to go from xmin to xmax", which need to be changed to "set
the y-axis limits to go from ymin to ymax" for the other function.  The
%(axis_letter)s stuff is quite verbose, unfortunately, but it's the same
as in things like

        kwargs set the Text properties.  Valid properties are
        %(Text)s

This does make the docstring a bit harder to work with, but the
alternative is to write it out twice, and then ensure the two
near-copies are kept in sync as changes are made.  Now of course this
isn't impossible, but that kind of thing does add to the maintenance
burden in my experience.

Keeping the two copies of the code in sync requires effort, too, and
it's easy to miss things; e.g., set_xlim() includes

        self._process_unit_info(xdata=(xmin, xmax))

but set_ylim() doesn't --- which is correct?  panx() and zoomx() include

        xmin, xmax = self.viewLim.intervalx().get_bounds()

but pany() and zoomy() have no analogous calls --- which is correct?
The docstrings of set_xlabel() and set_ylabel() are formatted
differently (obviously this is not a critical problem but still), and
the examples in axhline() and axvline()'s docstrings are slightly
different.  There was the bug with hlines() and vlines() that lead to my
query.  I'm not trying to be annoying in pointing out these things, just
saying that although there is a bit of a learning curve in the 'only
write the function once' approach, I've found that it does save trouble
in the long run.

The metaclass itself is only really used to automate the process of
creating the two specific functions from the one generic one.  It could
be left out, instead meaning that you'd write something like

   class Axes:
      [...]

      def set__AXISLETTER_scale(self, [...]):
          # [...]
          # do stuff
          # [...]
      set_xscale = make_specific_function(set__AXISLETTER_scale, X_TRAITS)
      set_yscale = make_specific_function(set__AXISLETTER_scale, Y_TRAITS)

The make_specific_function() function is a little bit hairy and there
might well be a better way to do it.  My current implementation fiddles
with the bytecode of the general function, and while I think it works
correctly, this kind of malarkey can certainly lead to bugs which are
very difficult to unravel if the "behind the scenes" stuff (i.e.,
make_specific_function() in this case) has problems.  Without using
something like make_specific_function(), you could do something more
like John's suggestion:

def set_xscale(self, value, basex = 10, subsx = None):
    """
    [...] x docstring [...]
    """
    self._set_scale(self.X_TRAITS, value, basex, subsx)

def set_yscale(self, value, basey = 10, subsy = None):
    """
    [...] y docstring [...]
    """
    self._set_scale(self.Y_TRAITS, value, basey, subsy)

def _set_scale(self, traits, value, base, subs):
    assert(value.lower() in ('log', 'linear', ))
    if value == 'log':
        traits.axis.set_major_locator(mticker.LogLocator(base))
        traits.axis.set_major_formatter(mticker.LogFormatterMathtext(base))
        traits.axis.set_minor_locator(mticker.LogLocator(base, subs))
        traits.transfunc.set_type(mtrans.LOG10)
        min_val, max_val = traits.limfunc()
        if min(min_val, max_val) <= 0:
            self.autoscale_view()
    elif value == 'linear':
        traits.axis.set_major_locator(mticker.AutoLocator())
        traits.axis.set_major_formatter(mticker.ScalarFormatter())
        traits.axis.set_minor_locator(mticker.NullLocator())
        traits.axis.set_minor_formatter(mticker.NullFormatter())
        traits.transfunc.set_type(mtrans.IDENTITY)

where the 'traits' stuff allows you to pass around the 'limfunc =
self.get_xlim' etc. all in one go.  'X_TRAITS' and 'Y_TRAITS' would be
set up in __init__().  Maybe 'X_METHODS' and 'Y_METHODS' would be better
names.  With the above approach, you do still end up with two
near-copies of the docstring but at least the actual code is all in one
place.

Anyway.  Just thought I'd make the suggestion.  If there's interest I
can tidy up what I've got and post it.  Or a patch which takes the
last-mentioned approach above.

Ben.

-------------------------------------------------------------------------
This SF.net email is sponsored by DB2 Express
Download DB2 Express C - the FREE version of DB2 express and take
control of your XML. No limits. Just data. Click to get it now.
http://sourceforge.net/powerbar/db2/
_______________________________________________
Matplotlib-devel mailing list
Matplotlib-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-devel

Reply via email to