Design Pattern in Python (1): Singleton
Tags: python, design, singleton, pattern
Published: 2020-12-02 13:13:50

## Background 

It was several years ago that I have learned and used **Design Patterns** during my previous job. We developed software for medical robots in **C++** (i.e OOP) and different design patterns were frequently used in our code. 
Later on, when I have started working in our startup, we develop a lot in **Python** and **HTML5** for BigData & Web projects thus we use hardly design patterns.  

Recently I am reading this book about **Design Patterns** as shown below:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/giw8weugvh8hqbkee6cg.png)

This book is very interesting and it makes me recall the knowledge on design patterns. I realize that it's time for me to do some design pattern exercises in **Python**.  


## Singleton exercise

So today I tried a simple scenario for implementing **Singleton** in **Python**. I want to simulate the case when an user calls siri to play a music item. 

GO! 

### No Singleton Case
Define a regular player class without singleton implementation.

```python
import threading
import time

## a global album dict containing three music as well as their length 
album = {"Love Story":10, "Angels":12, "Elle":18}

### a regular MusicPlayer class which contains a thread for simulating music playing
### 普通的播放器类，自带一个播放线程
class musicplayer():      
    """
        a regular MusicPlayer class 
    """
    
    def __init__(self):
        self.__currentItem = ""
        self.__musicLength = 0
        self.__timer = 0
        self.__thread = threading.Thread(target=self.playsound)

        
    def play(self):
        if  self.__thread.isAlive():
            pass
        else:
            self.__thread.start()
        
    def updateItem(self, musicfile, length):
        self.__currentItem = musicfile
        self.__musicLength = length
        self.__timer = 0
        
    def playsound(self):

        while self.__timer <= self.__musicLength:
            print(" -- playing   {}  ---- {}.00/{}.00" .format(self.__currentItem, self.__timer, self.__musicLength))
            time.sleep(1)
            self.__timer +=1
        print("{} playing thread has ended".format(self.__currentItem))
        
```
Now define a quite simple Siri class which has only one function `runCMD` for initializing player app and playing music.
```python
class Siri():

    def __init__(self):
        pass
        
    def runCMD(self, cmd):
        if cmd.startswith("play music"):
            music = cmd.replace("play music", "").strip()
            player = musicplayer()
            player.updateItem(music, album[music])
            player.play()
        
```
Now try it in a **main** function.
```python
if __name__ == "__main__":
    musicList = list(album.keys())
    siri = Siri()
    siri.runCMD("play music {}".format(musicList[0]))
    time.sleep(4)
    siri.runCMD("play music {}".format(musicList[1]))
    
    mainTime = 0
    
    while (mainTime < 20):
        mainTime+=1
        time.sleep(1)
    
    print("End of simulation")
```
Execute the code above, I see that two music players have been created and they played different items at the same time.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/z6g5uwgwn9ksoqtd2qjt.PNG)

### Singleton Case

Now I update a little bit the music player class so that it is a singleton-pattern class. This class is derived from **object** class so that I need to define `__new__()` and `__init__()` functions to do the constructor's work.

```python
class musicplayer(object):      
    """
        ### a SINGLETON MusicPlayer class which contains a thread for simulating music playing
    """
    __instance = None
    __isInstanceDefined = False

    def __new__(cls):
        if not cls.__instance:
            musicplayer.__instance = super().__new__(cls)
        return cls.__instance

    def __init__(self):
        if not musicplayer.__isInstanceDefined:  
            self.__currentItem = ""
            self.__musicLength = 0
            self.__timer = 0
            self.__thread = threading.Thread(target=self.playsound)
            musicplayer.__isInstanceDefined = True
        else:
            pass

    
    def play(self):
        if  self.__thread.isAlive():
            pass
        else:
            self.__thread.start()
        
    def updateItem(self, musicfile, length):
        self.__currentItem = musicfile
        self.__musicLength = length
        self.__timer = 0
        
    def playsound(self):

        while self.__timer <= self.__musicLength:
            print(" -- playing   {}  ---- {}.00/{}.00" .format(self.__currentItem, self.__timer, self.__musicLength))
            time.sleep(1)
            self.__timer +=1
        print("{} playing thread has ended".format(self.__currentItem))
```
Now execute again the entire code file and it works. There is one and only one player instance in the simulation even siri changed the music item:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/f5r7srbrug1j7r52yv4v.PNG)


### Singleton Decorator
In the book I read, the author introduced a very graceful way to realize **Singleton** pattern without modification of existing classes: using Decorator. I like this method very much.

This decorator method is explicit and simple:
```python
def singletonDecorator(cls, *args, **kwargs):
    """ defines a Singleton decorator """
    instance = {}

    def wrapperSingleton(*args, **kwargs):
        if cls not in instance:
            instance[cls] = cls(*args, **kwargs)
        return instance[cls]

    return wrapperSingleton
```
Now I put my entire code file here. Note that there is `@singletonDecorator` declaration above my existing `musicplayer` class:
```python
import threading
import time

album = {"Love Story":10, "Angels":12, "Elle":18}


def singletonDecorator(cls, *args, **kwargs):
    """ defines a Singleton decorator """
    instance = {}

    def wrapperSingleton(*args, **kwargs):
        if cls not in instance:
            instance[cls] = cls(*args, **kwargs)
        return instance[cls]

    return wrapperSingleton

### a regular MusicPlayer class which contains a thread for simulating music playing
### 普通的播放器类，自带一个播放线程
@singletonDecorator
class musicplayer():         
    """
        a regular MusicPlayer class 
    """
    
    def __init__(self):
        self.__currentItem = ""
        self.__musicLength = 0
        self.__timer = 0
        self.__thread = threading.Thread(target=self.playsound)

        
    def play(self):
        if  self.__thread.isAlive():
            pass
        else:
            self.__thread.start()
        
    def updateItem(self, musicfile, length):
        self.__currentItem = musicfile
        self.__musicLength = length
        self.__timer = 0
        
    def playsound(self):

        while self.__timer <= self.__musicLength:
            print(" -- playing   {}  ---- {}.00/{}.00" .format(self.__currentItem, self.__timer, self.__musicLength))
            time.sleep(1)
            self.__timer +=1
        print("{} playing thread has ended".format(self.__currentItem))

class Siri():

    def __init__(self):
        pass
        
    def runCMD(self, cmd):
        if cmd.startswith("play music"):
            music = cmd.replace("play music", "").strip()
            player = musicplayer() 
            player.updateItem(music, album[music])
            player.play()
        


if __name__ == "__main__":


    musicList = list(album.keys())
    siri = Siri()
    siri.runCMD("play music {}".format(musicList[0]))
    time.sleep(4)
    siri.runCMD("play music {}".format(musicList[1]))
    
    mainTime = 0

    
    while (mainTime < 20):
        mainTime+=1
        time.sleep(1)
    
    print("End of simulation")
```
This new method gives the same result: 
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/f5r7srbrug1j7r52yv4v.PNG)