#59 classes

Hi all,

With apologies for the delay (partly as I was thinking about this and trying some things out), I've tried to come up with a class inheritance model for top level structures at the Python layer of GalSim, as we have discussed on the list.

I've copied much from the structure of the SBProfiles, trying to benefit from inheritance in the same way, while also trying to reconcile this with each of our Python classes having a "has a" relationship to an SBProfile, rather than an "is a" relationship, since Jim suggested that there are some subtleties to this with wrapped C++.

I've defined a base class, GSObject, which is like SBProfile in that it is never instantiated directly but defines a reference interface and methods for derived classes to add-to/override if they wish.  The first thing this base class does in its _ _ init _ _ method (\simeq constructor for C++ types) is give itself an `SBProfile` attribute, thus all of our derived classes will "have a" SBProfile.

I then go on to define the reference set of class methods, much as is done in the C++ SBProfile.  These are no more than calls to self.SBProfile.method_name(), but it seemed overall slightly preferable to have these methods available directly without needing to go via the middleman of the SBProfile attribute (it would be totally valid form to do that in Python, attributes are as public as anything else in the Python, non-encapsulating paradigm).  The price paid for this is that any changes made in the SBProfile basic methods must also be reflected here in `base.py`. This is open to discussion.

Then, I define some of the derived classes that we actually construct, and will want for the first Milestone.  The *interface* is exactly the same as an SBProfile object, since they have inherited GSObject which mimics this, but again they only "have a" SBProfile rather than are one.  They will all have an attribute called `SBProfile`, which will contain an `SBProfile` object of the respective type (which can easily be queries), and inherit all of the methods of `GSObject`.

An example from an ipython session:

    In [1]: import galsim

    In [2]: a = galsim.GSAiry()

    In [3]: a.<TAB-KEY>
    a.SBProfile       a.draw            a.isAxisymmetric  a.rotate          a.shift
    a.centroid        a.getFlux         a.maxK            a.setFlux         a.shoot
    a.distort         a.isAnalyticX     a.nyquistDx       a.shear           a.stepK

^^ this shows the methods available, and the ubiquitous SBProfile attribute.

Taking a look at this SBProfile attribute, we see it is of type SBAiry...

    In [4]: a.SBProfile?
    Type:       SBAiry 
    Base Class: <class 'galsim._galsim.SBAiry'>
    String Form:<galsim._galsim.SBAiry object at 0x10b2ce5d0>
    Namespace:  Interactive
    File:       /Users/browe/great3/GalSim/galsim/_galsim.so
    Docstring:  <no docstring>
    Constructor Docstring:
    __init__( (object)arg1 [, (float)D=1.0 [, (float)obs=1.0 [, (float)flux=1.0]]]) -> None :

        C++ signature :
            void __init__(_object* [,double=1.0 [,double=1.0 [,double=1.0]]])

Clearly some work needs to be done on the docstrings, but you get the idea!  For the derived classes we can then define the extra methods special to each class in the SBProfile C++ code, in a totally analagous way.

I think overall approach this combines the benefits of inheriting a shared interface for the SBProfiles, while not inheriting them directly.  However, there is a price to be paid which is we will have to ensure that internal changes in the SBProfile classes will have to be mirrored here in Python, or we'll start getting lots of exceptions.

Comments welcomed!

P.S. Currently I've put this in a file `base.py` within galsim/ and imported all from this via __init__.py at the moment the user hits `import galsim`.  This practice (and the name of base.py) are all free to change: I'd welcome any comments on package handling from more seasoned Pythonistas.