单例模型(learning_python_design_patterns)


Singleton

很多情况下 在程序运行周期中, 至始至终只需要一个数据实例

类对象, 一个list或者dict

什么时候使用

控制 并发请求共享资源

系统资源的全局访问

只用一个实例对象

几个典型的使用场景

• The logging class and its subclasses (global point of access for the logging
  class to send messages to log)

• Printer spooler (your application should only have a single instance of the
  spooler in order to avoid having a conflicting request for the same resource)
  
• Managing a connection to a database

• File manager

• Retrieving and storing information on external configuration files

• Read-only singletons storing some global states (user language, time, time
   zone, application path, and so on)   

A module-level singleton python模块是单例的

下面是python一个模块的导入过程

1. Check whether a module is already imported.
2. If yes, return it.
3. If not, find a module, initialize it, and return it.
4. Initializing a module means executing code, including all module-level
assignments. When you import the module for the first time,
all initializations are done; however, if you try to import the module for
the second time, Python will return the initialized module.
Thus, the initialization will not be done, and you get a previously imported
module with all of its data

所以在python中 可以把数据作为模块的属性, 来实现单例模式



# singletone.py:
 only_one_var = "I'm only one var"
 
# module1.py:
import singletone
print singleton.only_one_var
singletone.only_one_var += " after modification"
import module2

# module2.py:
import singletone
print singleton.only_one_var

缺点

容易出错

占用 module 本身的 namespace

不支持懒加载, 每次导入都会全部加载

• It's ugly, especially if you have a lot of objects that should remain as singletons.
• It pollutes the module namespace with unnecessary variables.
• They don't permit lazy allocation and initialization; all global variables will
  be loaded during the module import process.
• It's not possible to reuse the code because you cannot use the inheritance.
• It has no special methods and no object-oriented programming benefits at all

A classic singleton 类单例模式

在创建实例的时候, 检查是否存在实例 (作为类的一个属性), 没有便创建返回


class Singleton(object):
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, 'instance'):
            cls.instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
        return cls.instance


single1 = Singleton()
single2 = Singleton()

print(single1 is single2)

single1.only_one_var = 'only one'

print(single2.only_one_var)

当然, 继承之后的类也是单例模式


class Sub(Singleton):
    pass

但是不是单态模式


s = Sub()

print(s is single1)
# False

The borg singleton 单态模式monostate

所有的实例都不同, 但共享相同的属性


class Borg(object):
    _shared_state = {}

    def __new__(cls, *args, **kwargs):
        obj = super(Borg, cls).__new__(cls, *args, **kwargs)
        obj.__dict__ = cls._shared_state
        return obj



class Child(Borg):
    pass

c = Child()

b = Borg()

print(b is c)  # False
b.only_one_var = "only one"
print(vars(b))    
print(c.__dict__)

不需要共享, 在繼承的時候重新定義_shared_state = {}

class Borg(object):
    _shared_state = {}

    def __new__(cls, *args, **kwargs):
        obj = super(Borg, cls).__new__(cls, *args, **kwargs)
        obj.__dict__ = cls._shared_state
        return obj



class Child(Borg):
    _shared_state = {}

c = Child()

b = Borg()

print(b is c)
b.only_one_var = "only one"
print(vars(b))
print(c.__dict__)

實例


import httplib2
import os
import re
import threading
import urllib
from urlparse import urlparse, urljoin

from BeautifulSoup import BeautifulSoup

#  Singleton
class Singleton(object):
    def __new__(cls):
    if not hasattr(cls, 'instance'):
        cls.instance = super(Singleton, cls).__new__(cls)
    return cls.instance


#  class for creating a thread.
class ImageDownloaderThread(threading.Thread):
    def __init__(self, thread_id, name, counter):
        threading.Thread.__init__(self)
        self.name = name
    def run(self):
        print ('Starting thread ', self.name)
        download_images(self.name)
        print ('Finished thread ', self.name)


# BFS algorithm, finds links 深度優先

def traverse_site(max_links=10):
    link_parser_singleton = Singleton()
    # While we have pages to parse in queue
    while link_parser_singleton.queue_to_parse:
    # If collected enough links to download images, return
        if len(link_parser_singleton.to_visit) == max_links:
            return
        url = link_parser_singleton.queue_to_parse.pop()
        http = httplib2.Http()
        try:
            status, response = http.request(url)
        except Exception:
            continue

        # Skip if not a web page
        if status.get('content-type') != 'text/html':
            continue
        # Add the link to queue for downloading images
        link_parser_singleton.to_visit.add(url)
        print('Added', url, 'to queue')
        bs = BeautifulSoup(response)
        for link in BeautifulSoup.findAll(bs, 'a'):
            link_url = link.get('href')
        # <img> tag may not contain href attribute
            if not link_url:
                continue
            parsed = urlparse(link_url)
        # If link follows to external webpage, skip it
            if parsed.netloc and parsed.netloc != parsed_root.netloc:
                continue
        # Construct a full url from a link which can be relative
            link_url = (parsed.scheme or parsed_root.scheme) + '://' + (parsed.netloc or parsed_root.netloc) + parsed.path or ''
        # If link was added previously, skip it
            if link_url in link_parser_singleton.to_visit:
                continue
        # Add a link for further parsing
            link_parser_singleton.queue_to_parse = [link_url] + link_parser_singleton.queue_to_parse



# 
def download_images(thread_name):
    singleton = Singleton()
    # While we have pages where we have not download images
    while singleton.to_visit:
        url = singleton.to_visit.pop()
        http = httplib2.Http()
        print(hread_name, 'Starting downloading images from', url)
        try:
            status, response = http.request(url)
        except Exception:
            continue
        bs = BeautifulSoup(response)
        # Find all <img> tags
        images = BeautifulSoup.findAll(bs, 'img')
        for image in images:
        # Get image source url which can be absolute or relative
            src = image.get('src')
        # Construct a full url. If the image url is relative,
        # it will be prepended with webpage domain.
        # If the image url is absolute, it will remain as is
            src = urljoin(url, src)
        # Get a base name, for example 'image.png' to name file locally
            basename = os.path.basename(src)
            if src not in singleton.downloaded:
                singleton.downloaded.add(src)
                print('Downloading', src)
        # Download image to local filesystem
                urllib.urlretrieve(src, os.path.join('images', basename))
        print(thread_name, 'finished downloading images from', url)


# main.py


if __name__ == '__main__':
    root = 'http://python.org'
    parsed_root = urlparse(root)
    singleton = Singleton()
    singleton.queue_to_parse = [root]
    # A set of urls to download images from
    singleton.to_visit = set()
    # Downloaded images
    singleton.downloaded = set()
    traverse_site()
    # Create images directory if not exists
    if not os.path.exists('images'):
        os.makedirs('images')
    # Create new threads
    thread1 = ImageDownloaderThread(1, "Thread-1", 1)
    thread2 = ImageDownloaderThread(2, "Thread-2", 2)
    # Start new Threads
    thread1.start()
    thread2.start()
Buy me a 肥仔水!