mailRe: The singleton design pattern for the old 'self.relax.data' data structure.


Others Months | Index by Date | Thread Index
>>   [Date Prev] [Date Next] [Thread Prev] [Thread Next]

Header


Content

Posted by Edward d'Auvergne on March 09, 2007 - 08:21:
On 3/8/07, Chris MacRaild <c.a.macraild@xxxxxxxxxxx> wrote:
On Thu, 2007-03-08 at 14:03 +1100, Edward d'Auvergne wrote:
> On 3/7/07, Chris MacRaild <c.a.macraild@xxxxxxxxxxx> wrote:
> > On Wed, 2007-03-07 at 19:06 +1100, Edward d'Auvergne wrote:
> >
> > > Hi,
> > >
> > > After careful thought about design patterns, I've decided to try to
> > > use the singleton pattern for the old 'self.relax.data' data
> > > structure.  See http://en.wikipedia.org/wiki/Singleton_pattern for
> > > more information about this design pattern.  I'll try to use the
> > > second simple example under the heading 'Python Borg pattern'.  The
> > > benefit of this pattern is that each module can use the code:
> > >
> > > from data import Data
> > > relax_data = Data()
> > >
> > > As Data will be a singleton if two modules used by relax instantiate
> > > the Data class then the global 'relax_data' in both modules will be
> > > the same instance.  Therefore if a method from the
> > > 'special_fns.model_free' module modifies the data structure, all the
> > > other relax modules using the singleton will see the changes.  The
> > > benefit of this pattern is that the data structure is similar in
> > > concept to a global variable but only modules utilising it will have
> > > it in one of their namespaces.  Also 'self.relax.data' will not need
> > > to be passed around inside the program, simplifying the code.  What do
> > > you think of the idea?
> >
> > One issue here, identified on the wikipedia page, is that __init__() is
> > called for each call of Singleton(). Therefore all of the standard
> > __init__() stuff - inialising variables and empty containers - will
> > happen every time the Singleton instance is sought. This is clearly not
> > what we want. Ofcourse there are many ways around that by cleverly
> > hiding the initialisation stuff, but its starting to look like a complex
> > solution to what should be a simple problem.
>
> I am aware of the execution of __init__() but this won't be an issue
> in the final construction of the relax data storage object.  The
> object will be multilayered.  The first layer, equivalent to the
> current top level Data class, will be a dictionary type and it's
> __init__() method will be empty.  There may be a few variables
> (current_run, etc.) and methods defined inside this object but these
> will be in the scope of the class rather than the __init__() method.
>
> The second layer will be data containers identified by the keys of the
> top level dictionary type object.  This layer's __init__() method will
> not be called each time.  The current Data.__init__() method will be
> shifted to this layer (actually I'll probably rename Data to
> RunContainer and then create a new Data object).  I'm still unsure but
> I think I'll try to leave all __init__() functions empty and define
> initial variables and objects in the scope of the class, eg:
>
> class RunContainer:
>     # The molecule-residue-spin object.
>     mol = MoleculeList()
>
>     # The diffusion tensor object.
>     diff = DiffContainer()
>
>     # Molecular structure.
>     structure = StructureContainer()
>
>     def __init__():
>         pass
>
>
> The third layer will be data containers for specific data types.  For
> example the diffusion tensor data structure, the Scientific PDB data
> structures (and future molecular structure data structures), and the
> molecule-residue-spin multi-object structure (each of these three
> layers will consist of two objects - a list type object whereby each
> element is a specific data storage object).
>
>
> > Something like:
> >
> > class Data:
> >     ...
> > Data = Data()
> >
> > in the data module, then everywhere else:
> >
> > from data import Data as relax_data
> >
> >
> > By rebinding the name 'Data' with an instance of the class, we
> > effectively prevent accidental creation of additional instances, and the
> > import makes that instance availible wherever we need it.
>
> I do think that the relax data storage object is the absolute ideal
> situation for the implementation of the Singleton design pattern.
> There is only one instance of this object ever and all parts of relax
> should worship it - relax is designed around this object.  It is the
> pillar that props up all of the program.  And the relax saved stated
> is a copy of this object.
>
> The Singleton construction is also very powerful for relax scripts.
> It allows the script access to the data storage object without needing
> to pass the object to the script.  Importantly it also allows the unit
> test framework access to the structure without invoking relax itself.
> And it is also very easy to implement cleanly - just have a look at
> the current 1.3 line code base.  The relax data storage object is now
> implemented as a Singleton and almost all of the relax test suite
> passes.  The only failure is in one functional test whereby a saved
> program state is loaded - this is because the state.load() user
> function is not working yet.

I agree with all of this, I'm just pointing out that we can get the
desired behaviour out of a very simple idiom, without having to rely on
a more complex implimentation of the design pattern. Admittedly it's not
that complex, but we should always be aiming for the simplest possible.

Alex Martelli has written some good stuff on these on these topics:
http://www.aleax.it/python_mat_en.html and especially
http://www.aleax.it/Python/5ep.html [not coincidentally, the credit for
the idiom I suggest also goes to Martelli, this time the Python
Cookbook]

I've had a read of http://www.aleax.it/Python/5ep.html and all that is is Alex bagging the singleton and instead presenting his alternative, the Python Borg pattern. Essentially from the perspective of the user, you really won't use the objects differently. If there are two objects A and B, if they are both singletons then A == B returns true. If they share the same 'state' as in Alex's Borg pattern then A == B returns false. However if they both have the variable x then in both situations A.x == B.x.

Now because the top level object is a dictionary type, using the Borg
pattern, A would have different keys to B (the keys are the names of
the data pipes, aka runs).  For our object, the Borg pattern cannot be
used.  Here is a simple example, which can be cut and paste into the
python interpreter, demonstrating the problem with the Borg pattern
with dictionary type objects:

from types import DictType

class Borg(DictType):
   __shared_state = {}
   def __init__(self):
       self.__dict__ = self.__shared_state

class Container:
   x = 1

A = Borg()
B = Borg()
A['y'] = Container
print A
print B
print A['y'].x
print B['y'].x

Edward



Related Messages


Powered by MHonArc, Updated Sat Mar 10 00:40:15 2007