coding guide

Eric Firing had the excellent idea of making a CODING_GUIDE which
summarizes the conventions used in matplotlib development, and I've
added this to svn. Feel free to make changes, additions and
comments. I think we could add a lot here, including an overview of
the API. Here is the document I just committed::

Devs, feel free to edit this document. This is meant to be a guide to
developers on the mpl coding practices and standards

== Committing Changes ==

When committing changes to matplotlib, there are a few things to bear
in mind.

  * if your changes are nontrivial, please make an entry in the
    CHANGELOG

  * if you change the API, please document it in API_CHANGES, and
    consider posing to mpl-devel

  * Are your changes python2.3 compatible? We are still trying to
    support 2.3, so avoid 2.4 only features like decorators until we
    remove 2.3 support

  * Are your changes Numeric, numarray and numpy compatible? Try
    running simple_plot.py or image_demo.py with --Numeric, --numarray
    and --numpy (Note, someone should add examples to
    backend_driver.py which explicitly require numpy, numarray and
    Numeric so we can automatically catch these)

  * Can you pass examples/backend_driver.py? This is our poor man's
    unit test.

  * If you have altered extension code, do you pass
    unit/memleak_hawaii.py?

== Naming conventions ==

  functions and class methods : lower or lower_underscore_separated

  attributes and variables : lower or lowerUpper

  classes : Upper or MixedCase

Personally, I prefer the shortest names that are still readable.

== kwargs processing ==

Matplotlib makes extensive use of **kwargs for pass through
customizations from one function to another, eg the pylab plot ->
Axes.plot pass through. As a general rule, the use of **kwargs should
be reserved for pass-through keyword arguments, eg

  def somefunc(x, k1='something', **kwargs):
      # do some thing with x, k1
      return some_other_func(..., **kwargs)

If I intend for all the keyword args to be used in somefunc alone, I
just use the key/value keyword args in the function definition rather
than the **kwargs idiom. In some cases I want to consume some keys
and pass through the others, in which case I pop the ones I want to
use locally and pass on the rest, eg I pop scalex and scaley in
Axes.plot and assume the rest are Line2D keyword arguments. Whenever
you mutate a kwargs dictionary (eg by popping it), you must first copy
it since the user may be explitly passing in a dictionary which is
used across many function calls. As an example of a copy, pop,
passthrough usage, see Axes.plot:

    def plot(self, *args, **kwargs):
        kwargs = kwargs.copy()
        scalex = popd(kwargs, 'scalex', True)
        scaley = popd(kwargs, 'scaley', True)
        if not self._hold: self.cla()
        lines = []
        for line in self._get_lines(*args, **kwargs):
            self.add_line(line)
            lines.append(line)

popd is a matplotlib.cbook function to pop an item from a dictionary
with a default value if the item doesn't exist

Note there is a use case when kwargs are meant to be used locally in
the function (not passed on), but you still need the **kwargs idiom.
That is when you want to use *args to allow variable numbers of
non-keyword args. In this case, python will not allow you to use
named keyword args after the *args usage, so you will be forced to use
**kwargs. An example is matplotlib.contour.ContourLabeler.clabel

    def clabel(self, *args, **kwargs):
        fontsize = kwargs.get('fontsize', None)
        inline = kwargs.get('inline', 1)
        self.fmt = kwargs.get('fmt', '%1.3f')
        colors = kwargs.get('colors', None)
        if len(args) == 0:
            levels = self.levels
            indices = range(len(self.levels))
        elif len(args) == 1:
     ...etc...

== class documentation ==

matplotlib uses artist instrospection of docstrings to support
properties. All properties that you want to support through setp and
getp should have a set_property and get_property method in the Artist
class. Yes this is not ideal given python properties or enthought
traits, but it is a historical legacy for now. The setter methods use
the docstring with the ACCEPTS token to indicate the type of argument
the method accepts. Eg in matplotlib.lines.Line2D

    def set_linestyle(self, linestyle):
        """
        Set the linestyle of the line

        ACCEPTS: [ '-' | '--' | '-.' | ':' | 'steps' | 'None' | ' ' | '' ]
        """

Since matplotlib uses a lot of pass through kwargs, eg in every
function that creates a line (plot, semilogx, semilogy, etc...), it
can be difficult for the new user to know which kwargs are supported.
I have developed a docstring interpolation scheme to support
documentation of every function that takes a **kwargs. The
requirements are:

  1) single point of configuration so changes to the properties don't
     require multiple docstring edits

  2) as automated as possible so that as properties change the docs
     are updated automagically.

I have added a matplotlib.artist.kwdocd to faciliate this. This
combines python string interpolation in the docstring with the
matplotlib artist introspection facility that underlies setp and getp.
The kwdocd is a single dictionary that maps class name to a docstring
of kwargs. Here is an example at the bottom of matplotlib.lines

artist.kwdocd['Line2D'] = '\n'.join(artist.ArtistInspector(Line2D).pprint_setters(leadingspace=12))

Then in any function accepting Line2D passthrough kwargs, eg
matplotlib.axes.Axes.plot

    def plot(self, *args, **kwargs):
        """
  Some stuff omitted

        The kwargs are Line2D properties:
%(Line2D)s

        kwargs scalex and scaley, if defined, are passed on
        to autoscale_view to determine whether the x and y axes are
        autoscaled; default True. See Axes.autoscale_view for more
        information
        """
  pass
    plot.__doc__ = plot.__doc__ % artist.kwdocd

Note there is a problem for Artist __init__ methods, eg
Patch.__init__ which supports Patch kwargs, since the artist inspector
cannot work until the class is fully defined and we can't modify the
Patch.__init__.__doc__ docstring outside the class definition. I have
made some manual hacks in this case which violates the "single entry
point" requirement above; hopefully we'll find a more elegant solution
before too long

  * Are your changes python2.3 compatible? We are still trying to
    support 2.3, so avoid 2.4 only features like decorators until we
    remove 2.3 support

Good, I thought we were still restricted to 2.2!

Does this mean boilerplate.py should be updated? (Or maybe it isn't worth fiddling with it.)
# wrap the plot commands defined in axes. The code generated by this
# file is pasted into pylab.py. We did try to do this the smart way,
# with callable functions and new.function, but could never get the
# docstrings right for python2.2. See
http://groups-beta.google.com/group/comp.lang.python/browse_thread/thread/dcd63ec13096a0f6/17739e70ac6f710c?lnk=gst&q=dcd63ec13096a0f6&rnum=1#17739e70ac6f710c

Eric

John Hunter wrote:
[...]
I am in the process of changing your shiny new kwdocd machinery slightly. I added artist.kwdoc so that

artist.kwdocd['Line2D'] = '\n'.join(artist.ArtistInspector(Line2D).pprint_setters(leadingspace=12))

becomes

artist.kwdocd['Line2D'] = artist.kwdoc(Line2D)

Then in any function accepting Line2D passthrough kwargs, eg
matplotlib.axes.Axes.plot

    def plot(self, *args, **kwargs):
        """
  Some stuff omitted

        The kwargs are Line2D properties:
%(Line2D)s

and the line above is changed to be indented the same as the rest of the string (improves readability to my eye)

        kwargs scalex and scaley, if defined, are passed on
        to autoscale_view to determine whether the x and y axes are
        autoscaled; default True. See Axes.autoscale_view for more
        information
        """
  pass

because this

    plot.__doc__ = plot.__doc__ % artist.kwdocd

becomes
     plot.__doc__ = dedent(plot.__doc__) % artist.kwdocd

where dedent is now in cbook.py

I can continue making the necessary changes if that is OK with you, but I don't want our versions to get tangled up if you are still working on this aspect, or if you don't like the strategy modification outlined above. I committed dedent but not the other changes.

Eric