Singleton Pattern in Python – A Complete Guide

27 Aug, 2021
A Singleton pattern in python is a design pattern that allows you to create just one instance of a class, throughout the lifetime of a program. Using a singleton pattern has many benefits. A few of them are:
To limit concurrent access to a shared resource.
To create a global point of access for a resource.
To create just one instance of a class, throughout the lifetime of a program.
Different ways to implement a Singleton:
A singleton pattern can be implemented in three different ways. They are as follows:
Module-level Singleton
Classic Singleton
Borg Singleton
Module-level Singleton:
All modules are singleton, by definition.  Let’s create a simple module-level singleton where the data is shared among other modules. Here we will create three python files – singleton.py, sample_module1.py, and sample_module2.py – in which the other sample modules share a variable from singleton.py. 
## singleton.py
shared_variable = "Shared Variable"
 singleton.py
## samplemodule1.py
import singleton
print(singleton.shared_variable)
singleton.shared_variable += "(modified by samplemodule1)"
samplemodule1.py
##samplemodule2.py
import singleton
print(singleton.shared_variable)
samplemodule2.py
Let’s look into the output.
Here, the value changed by samplemodule1 is also reflected in samplemodule2.
Classic Singleton:
Classic Singleton creates an instance only if there is no instance created so far; otherwise, it will return the instance that is already created. Let’s take a look at the below code.
Python3
class SingletonClass(object):  def __new__(cls):    if not hasattr(cls, 'instance'):      cls.instance = super(SingletonClass, cls).__new__(cls)    return cls.instance   singleton = SingletonClass()new_singleton = SingletonClass() print(singleton is new_singleton) singleton.singl_variable = "Singleton Variable"print(new_singleton.singl_variable)
True
Singleton Variable
Here, in the __new__ method, we will check whether an instance is created or not. If created, it will return the instance; otherwise, it will create a new instance. You can notice that singleton and new_singleton return the same instance and have the same variable.
Let’s check what happens when we subclass a singleton class. 
Python3
class SingletonClass(object):  def __new__(cls):    if not hasattr(cls, 'instance'):      cls.instance = super(SingletonClass, cls).__new__(cls)    return cls.instance   class SingletonChild(SingletonClass):    pass   singleton = SingletonClass() child = SingletonChild()print(child is singleton) singleton.singl_variable = "Singleton Variable"print(child.singl_variable)
  
True
Singleton Variable
Here, you can see that SingletonChild has the same instance of SingletonClass and also shares the same state. But there are scenarios, where we need a different instance, but should share the same state. This state sharing can be achieved using Borg singleton.
Borg Singleton: 
Borg singleton is a design pattern in Python that allows state sharing for different instances. Let’s look into the following code.
Python3
class BorgSingleton(object):  _shared_borg_state = {}     def __new__(cls, *args, **kwargs):    obj = super(BorgSingleton, cls).__new__(cls, *args, **kwargs)    obj.__dict__ = cls._shared_borg_state    return obj   borg = BorgSingleton()borg.shared_variable = "Shared Variable" class ChildBorg(BorgSingleton):  pass childBorg = ChildBorg()print(childBorg is borg)print(childBorg.shared_variable)
False
Shared Variable
Along with the new instance creation process, a shared state is also defined in the __new__ method. Here the shared state is retained using the shared_borg_state attribute and it is stored in the __dict__ dictionary of each instance.
If you want a different state, then you can reset the  shared_borg_state attribute. Let’s see how to reset a shared state.
Python3
class BorgSingleton(object):  _shared_borg_state = {}     def __new__(cls, *args, **kwargs):    obj = super(BorgSingleton, cls).__new__(cls, *args, **kwargs)    obj.__dict__ = cls._shared_borg_state    return obj   borg = BorgSingleton()borg.shared_variable = "Shared Variable" class NewChildBorg(BorgSingleton):    _shared_borg_state = {} newChildBorg = NewChildBorg()print(newChildBorg.shared_variable)
Here, we have reset the shared state and tried to access the shared_variable. Let’s see the error.
Traceback (most recent call last):
  File "/home/329d68500c5916767fbaf351710ebb13.py", line 16, in <module>
    print(newChildBorg.shared_variable)
AttributeError: 'NewChildBorg' object has no attribute 'shared_variable'
Use cases of a Singleton:
Let’s list a few of the use cases of a singleton class. They are as follows:
Managing a database connection
Global point access to writing log messages
File Manager
Print spooler
Create a Web Crawler using Classic Singleton:
Let’s create a webcrawler that uses the benefit of a classic singleton. In this practical example, the crawler scans a webpage, fetch the links associated with the same website, and download all the images in it. Here, we have two main classes and two main functions.
CrawlerSingleton: This class acts a classic singleton
ParallelDownloader: This class provides thread functionality to download images
navigate_site: This function crawls the website and fetches the links that belong to the same website. And, finally, it arranges the link to download images.
download_images: This function crawls the page link and downloads the images.
Apart from the above classes and functions, we use two sets of libraries to parse the web page – BeautifulSoap and HTTP Client.
Have a look at the below code. 
Note: Execute the code in your local machine
Python3
import httplib2import osimport reimport threadingimport urllibimport urllib.requestfrom urllib.parse import urlparse, urljoinfrom bs4 import BeautifulSoup class CrawlerSingleton(object):    def __new__(cls):        """ creates a singleton object, if it is not created,        or else returns the previous singleton object"""        if not hasattr(cls, 'instance'):            cls.instance = super(CrawlerSingleton, cls).__new__(cls)        return cls.instance def navigate_site(max_links = 5):    """ navigate the website using BFS algorithm, find links and        arrange them for downloading images """     # singleton instance    parser_crawlersingleton = CrawlerSingleton()         # During the initial stage, url_queue has the main_url.    # Upon parsing the main_url page, new links that belong to the    # same website is added to the url_queue until    # it equals to max _links.    while parser_crawlersingleton.url_queue:         # checks whether it reached the max. link        if len(parser_crawlersingleton.visited_url) == max_links:            return         # pop the url from the queue        url = parser_crawlersingleton.url_queue.pop()         # connect to the web page        http = httplib2.Http()        try:            status, response = http.request(url)        except Exception:            continue                 # add the link to download the images        parser_crawlersingleton.visited_url.add(url)        print(url)         # crawl the web page and fetch the links within        # the main page        bs = BeautifulSoup(response, "html.parser")         for link in BeautifulSoup.findAll(bs, 'a'):            link_url = link.get('href')            if not link_url:                continue             # parse the fetched link            parsed = urlparse(link_url)                         # skip the link, if it leads to an external page            if parsed.netloc and parsed.netloc != parsed_url.netloc:                continue             scheme = parsed_url.scheme            netloc = parsed.netloc or parsed_url.netloc            path = parsed.path                         # construct a full url            link_url = scheme +'://' +netloc + path                          # skip, if the link is already added            if link_url in parser_crawlersingleton.visited_url:                continue                         # Add the new link fetched,            # so that the while loop continues with next iteration.            parser_crawlersingleton.url_queue = [link_url] +\                                                parser_crawlersingleton.url_queue             class ParallelDownloader(threading.Thread):    """ Download the images parallelly """    def __init__(self, thread_id, name, counter):        threading.Thread.__init__(self)        self.name = name     def run(self):        print('Starting thread', self.name)        # function to download the images        download_images(self.name)        print('Finished thread', self.name)             def download_images(thread_name):    # singleton instance    singleton = CrawlerSingleton()    # visited_url has a set of URLs.    # Here we will fetch each URL and    # download the images in it.    while singleton.visited_url:        # pop the url to download the images        url = singleton.visited_url.pop()         http = httplib2.Http()        print(thread_name, 'Downloading images from', url)         try:            status, response = http.request(url)        except Exception:            continue         # parse the web page to find all images        bs = BeautifulSoup(response, "html.parser")         # Find all <img> tags        images = BeautifulSoup.findAll(bs, 'img')         for image in images:            src = image.get('src')            src = urljoin(url, src)             basename = os.path.basename(src)            print('basename:', basename)             if basename != '':                if src not in singleton.image_downloaded:                    singleton.image_downloaded.add(src)                    print('Downloading', src)                    # Download the images to local system                    urllib.request.urlretrieve(src, os.path.join('images', basename))                    print(thread_name, 'finished downloading images from', url) def main():    # singleton instance    crwSingltn = CrawlerSingleton()     # adding the url to the queue for parsing    crwSingltn.url_queue = [main_url]     # initializing a set to store all visited URLs    # for downloading images.    crwSingltn.visited_url = set()     # initializing a set to store path of the downloaded images    crwSingltn.image_downloaded = set()         # invoking the method to crawl the website    navigate_site()     ## create images directory if not exists    if not os.path.exists('images'):        os.makedirs('images')     thread1 = ParallelDownloader(1, "Thread-1", 1)    thread2 = ParallelDownloader(2, "Thread-2", 2)     # Start new threads    thread1.start()    thread2.start()      if __name__ == "__main__":    main_url = ("https://www.geeksforgeeks.org/")    parsed_url = urlparse(main_url)    main()
Let’s look into the downloaded images and python shell output.
Downloaded Images
Python Shell Output
Summary:
Singleton pattern is a design pattern in Python that restricts the instantiation of a class to one object. It can limit concurrent access to a shared resource, and also it helps to create a global point of access for a resource. 
anikaseth98
saurabh1990aror
clintra
python-design-pattern
Python
Writing code in comment?
Please use ide.geeksforgeeks.org,
generate link and share the link here.
How to Install PIP on Windows ?
Python Classes and Objects
Python OOPs Concepts
Introduction To PYTHON
How to drop one or multiple columns in Pandas Dataframe
Python | os.path.join() method
Check if element exists in list in Python
How To Convert Python Dictionary To JSON?
Python | Get unique values from a list
Python | datetime.timedelta() function