Un patrón Singleton en python es un patrón de diseño que le permite crear solo una instancia de una clase, a lo largo de la vida útil de un programa. El uso de un patrón singleton tiene muchos beneficios. Algunos de ellos son:
- Para limitar el acceso simultáneo a un recurso compartido.
- Para crear un punto de acceso global para un recurso.
- Para crear solo una instancia de una clase, a lo largo de la vida útil de un programa.
Diferentes formas de implementar un Singleton:
Un patrón singleton se puede implementar de tres maneras diferentes. Son los siguientes:
- Singleton a nivel de módulo
- Singleton clásico
- Borg Singleton
Singleton a nivel de módulo:
Todos los módulos son singleton, por definición. Vamos a crear un singleton simple a nivel de módulo donde los datos se comparten entre otros módulos. Aquí crearemos tres archivos python: singleton.py, sample_module1.py y sample_module2.py, en los que los otros módulos de muestra comparten una variable de singleton.py.
## singleton.py shared_variable = "Shared Variable"
singleton.py
## samplemodule1.py import singleton print(singleton.shared_variable) singleton.shared_variable += "(modified by samplemodule1)"
módulo de muestra1.py
##samplemodule2.py import singleton print(singleton.shared_variable)
samplemodule2.py
Echemos un vistazo a la salida.
Aquí, el valor cambiado por samplemodule1 también se refleja en samplemodule2.
Singleton clásico:
Classic Singleton crea una instancia solo si no se ha creado ninguna instancia hasta el momento; de lo contrario, devolverá la instancia que ya está creada. Echemos un vistazo al siguiente código.
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
Aquí, en el método __new__, comprobaremos si se crea una instancia o no. Si se crea, devolverá la instancia; de lo contrario, creará una nueva instancia. Puede notar que singleton y new_singleton devuelven la misma instancia y tienen la misma variable.
Veamos qué sucede cuando subclasificamos una clase singleton.
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
Aquí puede ver que SingletonChild tiene la misma instancia de SingletonClass y también comparte el mismo estado. Pero hay escenarios, donde necesitamos una instancia diferente, pero deberíamos compartir el mismo estado. Este estado compartido se puede lograr utilizando Borg singleton.
Borg Singleton:
Borg singleton es un patrón de diseño en Python que permite compartir estados para diferentes instancias. Analicemos el siguiente código.
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
Junto con el nuevo proceso de creación de instancias, también se define un estado compartido en el método __new__. Aquí, el estado compartido se conserva mediante el atributo shared_borg_state y se almacena en el diccionario __dict__ de cada instancia.
Si desea un estado diferente, puede restablecer el atributo shared_borg_state. Veamos cómo restablecer un estado compartido.
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)
Aquí, hemos restablecido el estado compartido e intentamos acceder a shared_variable. Veamos el 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'
Casos de uso de un Singleton:
Enumeremos algunos de los casos de uso de una clase singleton. Son los siguientes:
- Administrar una conexión de base de datos
- Punto de acceso global para escribir mensajes de registro
- Administrador de archivos
- Cola de impresión
Cree un rastreador web usando Classic Singleton:
Vamos a crear un rastreador web que use el beneficio de un singleton clásico. En este ejemplo práctico, el rastreador escanea una página web, busca los enlaces asociados con el mismo sitio web y descarga todas las imágenes que contiene. Aquí, tenemos dos clases principales y dos funciones principales.
- CrawlerSingleton: esta clase actúa como un singleton clásico
- ParallelDownloader: esta clase proporciona funcionalidad de subprocesos para descargar imágenes
- navegar_sitio: esta función rastrea el sitio web y obtiene los enlaces que pertenecen al mismo sitio web. Y, por último, dispone el enlace para descargar imágenes.
- download_images: esta función rastrea el enlace de la página y descarga las imágenes.
Además de las clases y funciones anteriores, utilizamos dos conjuntos de bibliotecas para analizar la página web: BeautifulSoap y HTTP Client .
Echa un vistazo al siguiente código.
Nota: Ejecute el código en su máquina local
Python3
import httplib2 import os import re import threading import urllib import urllib.request from urllib.parse import urlparse, urljoin from 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()
Veamos las imágenes descargadas y la salida de Python Shell.
Resumen:
El patrón Singleton es un patrón de diseño en Python que restringe la instanciación de una clase a un objeto. Puede limitar el acceso simultáneo a un recurso compartido y también ayuda a crear un punto de acceso global para un recurso.
Publicación traducida automáticamente
Artículo escrito por SonuGeorge y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA