Z. QIU
Posted on December 2, 2020
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:
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.
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.
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.
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.
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.
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:
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:
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:
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")
Posted on December 2, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.