Saturday 6 July 2013

Singletons in Python (and GSoC update)

This week, there isn't any interesting stuff to write about as far as the project is concerned, as I pretty much cleaned up what I had done in the previous two weeks. By 'clean up', I mean PEP-8 modifications to the existing code and rectification of some tiny(but serious) conceptual errors in the MovingRefFrame methods. I also spent quite some time reviewing, or rather, trying to understand Prasoon's code. I pulled his vector branch on my machine and started executing some tests on it. I have left some comments on his PR, and hopefully they will get sorted out soon. One good thing is, I also wrote the first draft of my code for Particle class-including methods for angular momentum, translational motion calculation, etc. This wasnt much of a problem, as I 'attached' a dedicated reference frame to each Particle that would be initialized. Hence, all of Particle's methods would call the relevant methods in MovingRefFrame.

I got to learn about a pretty neat method of implementing Singleton classes in Python. How? I saw the __new__ method being called in Prasoon's code, and after an hour of going from website to website, I somehow ended up learning this.

Have a look at the following code-
1:  class MutableSingletonClass(object):  
2:    instance = None  
3:      
4:    def __new__(cls, *args, **kwargs):  
5:      print ("In __new__")  
6:      instance = cls.instance  
7:      if instance is None:  
8:        print ("No instance found")  
9:        instance = cls.instance = \  
10:              object.__new__(cls)  
11:      else:  
12:        print ("Instance found")  
13:      return instance  
14:      
15:    def __init__(self, *args, **kwargs):  
16:      print ("In __init__")  
17:      pass  
18:    
19:  class Class1(MutableSingletonClass):  
20:    def __init__(self, x):  
21:      print ("In Class1 __init__")  
22:      self.x = x  

MutableSingletonClass's subclasses (and that class itself) will have only one instance being generated per session (the first time, that is). The next time you try to initialize a new instance of Class1, that same instance that was created earlier will be modified. Mind you, that one common instance WILL be modified everytime you call the constructor with new arguments. The reason this happens is, every time __new__ returns an object (which __new__ ensures is only one per session in this case), the __init__method is called. You cannot avoid that.
To get a better idea, look at the output-
1:  >>> c1 = Class1(3)  
2:  In __new__  
3:  No instance found  
4:  In Class1 __init__  
5:  >>> c2 = Class1(4)  
6:  In __new__  
7:  Instance found  
8:  In Class1 __init__  
9:  >>> c1.x  
10:  4  
11:  >>>c1 == c2  
12:  True  

Now, if we want to implement a real Singleton type, the Python docs show a nice method-

1:  class FixedSingletonClass(object):  
2:    instance = None  
3:      
4:    def __new__(cls, *args, **kwargs):  
5:      print ("In __new__")  
6:      instance = cls.instance  
7:      if instance is not None:  
8:        print ("Instance found")  
9:      else:  
10:        print ("No instance found")  
11:        instance = cls.instance = \  
12:              object.__new__(cls)  
13:        instance.init(*args, **kwargs)  
14:      return instance  
15:    
16:    def init(self, *args, **kwargs):  
17:      #This is where the subclasses should implement their  
18:      #initialization  
19:      pass  
20:      
21:    def __init__(self, *args, **kwargs):  
22:      #Subclasses should only define this as a dummy so users no which  
23:      #arguments to pass for initialization  
24:      print ("In __init__")  
25:      pass  
26:    
27:  class Class2(FixedSingletonClass):  
28:    def init(self, x):  
29:      print ("In Class2 init")  
30:      self.x = x  
31:    
32:    def __init__(self, x):  
33:      print("In Class2 __init__")  
34:      pass  

As you can see here, this implementation reduces the __init__ method to a mere formality/dummy (just to tell the users which values to enter for initialization, in the shell). Actually speaking, all the work is done by the 'init' method, which can now be controlled by the program. Hence, this real initializer (not __init__) is called only once..the first time an instance of Class2 is created. The rest of the times, __init__ is called, but it makes no changes to the existing instance. This ensures all references of Class2 point to a common instance.
Hence, the output-
1:  >>> d1 = Class2(5)  
2:  In __new__  
3:  No instance found  
4:  In Class2 init  
5:  In Class2 init  
6:  >>> d2 = Class2(10)  
7:  In __new__  
8:  Instance found  
9:  In Class2 init  
10:  >>> d1.x  
11:  5  
12:  >>> assert d1 is d2  

I hope this was useful.
That's all for now.
Have a great week ahead :-)

2 comments:

Aaron Meurer said...

Creating singletons of mutable classes is probably a bad idea.

You should take a look at how Singleton works in SymPy (sympy/core/singleton.py). It uses metaclasses to do perform singletonizing automagically. It also stores all instances in a registry, which is accessible in SymPy via the S object.

Unknown said...

I have to agree about mutable singletons being a bad idea. Old references won't know that the instance has changed, and may cause a lot of confusion.
The reason I posted about it is because that is the mistake I made when I tried to make a singleton on my own, so just made a note about what's not to be done :-)